import * as fs from 'fs';
import * as path from 'path';

import { omit } from 'lodash';
import { execSync } from 'child_process';
import { merge, isErrorResult } from 'openapi-merge';
import type { Swagger } from 'atlassian-openapi';
import { RESTRICTED_ROUTE_BASE_PATHS } from '@letta-cloud/sdk-core';

const lettaWebOpenAPIPath = path.join(
  __dirname,
  '..',
  '..',
  '..',
  'web',
  'autogenerated',
  'letta-web-openapi.json',
);
const lettaAgentsAPIPath = path.join(
  __dirname,
  '..',
  '..',
  'letta',
  'server',
  'openapi_letta.json',
);

const lettaWebOpenAPI = JSON.parse(
  fs.readFileSync(lettaWebOpenAPIPath, 'utf8'),
) as Swagger.SwaggerV3;
const lettaAgentsAPI = JSON.parse(
  fs.readFileSync(lettaAgentsAPIPath, 'utf8'),
) as Swagger.SwaggerV3;

// removes any routes that are restricted
lettaAgentsAPI.paths = Object.fromEntries(
  Object.entries(lettaAgentsAPI.paths).filter(([path]) =>
    RESTRICTED_ROUTE_BASE_PATHS.every(
      (restrictedPath) => !path.startsWith(restrictedPath),
    ),
  ),
);

const lettaAgentsAPIWithNoEndslash = Object.keys(lettaAgentsAPI.paths).reduce(
  (acc, path) => {
    const pathWithoutSlash = path.endsWith('/')
      ? path.slice(0, path.length - 1)
      : path;
    acc[pathWithoutSlash] = lettaAgentsAPI.paths[path];
    return acc;
  },
  {} as Swagger.SwaggerV3['paths'],
);

// remove duplicate paths, delete from letta-web-openapi if it exists in sdk-core
// some paths will have an extra / at the end, so we need to remove that as well
lettaWebOpenAPI.paths = Object.fromEntries(
  Object.entries(lettaWebOpenAPI.paths).filter(([path]) => {
    const pathWithoutSlash = path.endsWith('/')
      ? path.slice(0, path.length - 1)
      : path;
    return !lettaAgentsAPIWithNoEndslash[pathWithoutSlash];
  }),
);

const agentStatePathsToOverride: Array<[string, string]> = [
  ['/v1/templates/{project}/{template_version}/agents', '201'],
  ['/v1/agents/search', '200'],
];

for (const [path, responseCode] of agentStatePathsToOverride) {
  if (lettaWebOpenAPI.paths[path]?.post?.responses?.[responseCode]) {
    // Get direct reference to the schema object
    const responseSchema =
      lettaWebOpenAPI.paths[path].post.responses[responseCode];
    const contentSchema = responseSchema.content['application/json'].schema;

    // Replace the entire agents array schema with the reference
    if (contentSchema.properties?.agents) {
      contentSchema.properties.agents = {
        type: 'array',
        items: {
          $ref: '#/components/schemas/AgentState',
        },
      };
    }
  }
}

// go through the paths and remove "user_id"/"actor_id" from the headers
for (const path of Object.keys(lettaAgentsAPI.paths)) {
  for (const method of Object.keys(lettaAgentsAPI.paths[path])) {
    // @ts-expect-error - a
    if (lettaAgentsAPI.paths[path][method]?.parameters) {
      // @ts-expect-error - a
      lettaAgentsAPI.paths[path][method].parameters = lettaAgentsAPI.paths[
        path
      ][method].parameters.filter(
        (param: Record<string, string>) =>
          param.in !== 'header' ||
          (
            param.name !== 'user_id' &&
            param.name !== 'User-Agent' &&
            param.name !== 'X-Project-Id' &&
            param.name !== 'X-Letta-Source' &&
            param.name !== 'X-Stainless-Package-Version' &&
            !param.name.startsWith('X-Experimental') &&
            !param.name.startsWith('X-Billing')
          ),
      );
    }
  }
}

const result = merge([
  {
    oas: lettaAgentsAPI,
  },
  {
    oas: lettaWebOpenAPI,
  },
]);

if (isErrorResult(result)) {
  console.error(`${result.message} (${result.type})`);
  process.exit(1);
}

result.output.openapi = '3.1.0';
result.output.info = {
  title: 'Letta API',
  version: '1.0.0',
};

result.output.servers = [
  {
    url: 'https://app.letta.com',
    description: 'Letta Cloud',
  },
  {
    url: 'http://localhost:8283',
    description: 'Self-hosted',
  },
];

result.output.components = {
  ...result.output.components,
  securitySchemes: {
    bearerAuth: {
      type: 'http',
      scheme: 'bearer',
    },
  },
};

result.output.security = [
  ...(result.output.security || []),
  {
    bearerAuth: [],
  },
];

// omit all instances of "user_id" from the openapi.json file
function deepOmitPreserveArrays(obj: unknown, key: string): unknown {
  if (Array.isArray(obj)) {
    return obj.map((item) => deepOmitPreserveArrays(item, key));
  }

  if (typeof obj !== 'object' || obj === null) {
    return obj;
  }

  if (key in obj) {
    return omit(obj, key);
  }

  return Object.fromEntries(
    Object.entries(obj).map(([k, v]) => [k, deepOmitPreserveArrays(v, key)]),
  );
}

// eslint-disable-next-line  @typescript-eslint/ban-ts-comment
// @ts-ignore
result.output.components = deepOmitPreserveArrays(
  result.output.components,
  'user_id',
);

// eslint-disable-next-line  @typescript-eslint/ban-ts-comment
// @ts-ignore
result.output.components = deepOmitPreserveArrays(
  result.output.components,
  'actor_id',
);

// eslint-disable-next-line  @typescript-eslint/ban-ts-comment
// @ts-ignore
result.output.components = deepOmitPreserveArrays(
  result.output.components,
  'organization_id',
);

fs.writeFileSync(
  path.join(__dirname, '..', 'openapi.json'),
  JSON.stringify(result.output, null, 2),
);

function formatOpenAPIJson() {
  const openApiPath = path.join(__dirname, '..', 'openapi.json');

  try {
    execSync(`npx prettier --write "${openApiPath}"`, { stdio: 'inherit' });
    console.log('Successfully formatted openapi.json with Prettier');
  } catch (error) {
    console.error('Error formatting openapi.json:', error);
    process.exit(1);
  }
}

formatOpenAPIJson();
