/* eslint-disable @typescript-eslint/no-explicit-any */

interface ToolArg {
  name: string;
  description: string;
  type?: string;
  required: boolean;
  position: string; // path | query | header | cookie | body
  enum?: string[];
  items?: any;
  properties?: Record<string, any>;
}

interface RawTool {
  name: string;
  description: string;
  args: ToolArg[];
  requestTemplate: any;
  responseTemplate: any;
}

interface McpConfig {
  server: {
    name: string;
    securitySchemes: any[];
  };
  tools: RawTool[];
}

/**
 * Extract tools from an OpenAPI 3.x document.
 * Ported from console-ui/src/pages/AI/McpDetail/Swagger2Tools.js
 */
export function extractToolsFromOpenAPI(openapi: any): McpConfig {
  const mcpConfig: McpConfig = {
    server: { name: 'openapi-server', securitySchemes: [] },
    tools: [],
  };

  // Process security schemes
  if (openapi.components?.securitySchemes) {
    const schemes = openapi.components.securitySchemes;
    for (const name of Object.keys(schemes)) {
      const scheme = schemes[name];
      mcpConfig.server.securitySchemes.push({
        id: name,
        type: scheme.type,
        scheme: scheme.scheme,
        in: scheme.in,
        name: scheme.name,
      });
    }
    mcpConfig.server.securitySchemes.sort((a: any, b: any) => a.id.localeCompare(b.id));
  }

  // Process paths
  if (openapi.paths) {
    for (const path of Object.keys(openapi.paths)) {
      const pathItem = openapi.paths[path];
      const methods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'];
      for (const method of methods) {
        if (pathItem[method]) {
          const tool = convertOperation(path, method, pathItem[method], openapi.servers);
          tool.responseTemplate = createResponseTemplate(pathItem[method]);
          mcpConfig.tools.push(tool);
        }
      }
    }
  }

  mcpConfig.tools.sort((a, b) => a.name.localeCompare(b.name));
  return mcpConfig;
}

function convertOperation(path: string, method: string, operation: any, servers?: any[]): RawTool {
  let toolName = operation.operationId || '';
  if (!toolName) {
    const pathName = path.replace(/[{}]/g, '').replace(/\//g, '_');
    toolName = method.toLowerCase() + pathName.charAt(0).toUpperCase() + pathName.slice(1);
  }

  const tool: RawTool = {
    name: toolName,
    description: operation.summary || '',
    args: [],
    requestTemplate: createRequestTemplate(path, method, operation, servers),
    responseTemplate: {},
  };

  // Operation-level security
  if (operation.security?.length > 0) {
    const schemeNames = Object.keys(operation.security[0]);
    if (schemeNames.length > 0) {
      tool.requestTemplate.security = { id: schemeNames[0] };
    }
  }

  // Parameters -> args
  if (operation.parameters) {
    for (const param of operation.parameters) {
      const arg: ToolArg = {
        name: param.name,
        description: param.description || '',
        required: param.required || false,
        position: param.in,
      };
      if (param.schema) {
        arg.type = param.schema.type;
        if (param.schema.enum?.length > 0) arg.enum = param.schema.enum;
        if (param.schema.type === 'array' && param.schema.items) {
          arg.items = { type: param.schema.items.type };
        }
        if (param.schema.type === 'object' && param.schema.properties) {
          arg.properties = {};
          for (const pn of Object.keys(param.schema.properties)) {
            const p = param.schema.properties[pn];
            if (p) {
              arg.properties[pn] = { type: p.type };
              if (p.description) arg.properties[pn].description = p.description;
            }
          }
        }
      }
      tool.args.push(arg);
    }
  }

  // RequestBody -> args
  if (operation.requestBody?.content) {
    for (const contentType of Object.keys(operation.requestBody.content)) {
      const mediaType = operation.requestBody.content[contentType];
      if (!mediaType.schema) continue;
      const schema = mediaType.schema;

      if (
        (contentType.includes('application/json') ||
          contentType.includes('application/x-www-form-urlencoded')) &&
        schema.type === 'object' &&
        schema.properties
      ) {
        for (const propName of Object.keys(schema.properties)) {
          const prop = schema.properties[propName];
          if (!prop) continue;
          const arg: ToolArg = {
            name: propName,
            description: prop.description || '',
            type: prop.type,
            required: schema.required?.includes(propName) || false,
            position: 'body',
          };
          if (prop.enum?.length > 0) arg.enum = prop.enum;
          if (prop.type === 'array' && prop.items) {
            arg.items = {
              type: prop.items.type,
              description: prop.items.description || '',
            };
            if (prop.items.type === 'object' && prop.items.properties) {
              arg.items.properties = prop.items.properties;
            }
          }
          if (prop.type === 'object' && prop.properties) {
            arg.properties = {};
            for (const sn of Object.keys(prop.properties)) {
              const sp = prop.properties[sn];
              if (sp) {
                arg.properties[sn] = { type: sp.type, description: sp.description || '' };
                if (sp.default !== undefined) arg.properties[sn].default = sp.default;
                if (sp.enum) arg.properties[sn].enum = sp.enum;
              }
            }
          }
          if (!prop.type && prop.allOf?.length === 1) {
            arg.type = 'object';
            arg.properties = allOfHandle(prop.allOf[0]);
          }
          tool.args.push(arg);
        }
      }
      break; // only first content type
    }
  }

  tool.args.sort((a, b) => a.name.localeCompare(b.name));
  return tool;
}

function allOfHandle(schema: any): Record<string, any> {
  const properties: Record<string, any> = {};
  if (schema.type === 'object' && schema.properties) {
    for (const pn of Object.keys(schema.properties)) {
      const p = schema.properties[pn];
      if (p) {
        properties[pn] = { type: p.type };
        if (p.description) properties[pn].description = p.description;
        if (!p.type && p.allOf?.length === 1) {
          properties[pn].type = 'object';
          properties[pn].properties = allOfHandle(p.allOf[0]);
        }
      }
    }
  }
  return properties;
}

function createRequestTemplate(path: string, method: string, operation: any, servers?: any[]) {
  let serverURL = servers?.[0]?.url || '';
  if (typeof serverURL === 'string') {
    serverURL = serverURL.trim().replace(/\/+$/, '');
  }
  const normalizedPath = typeof path === 'string' ? `/${path}`.replace(/\/{2,}/g, '/').replace(/^\/+/, '/') : '';
  const fullUrl = serverURL + normalizedPath;

  const template: any = {
    url: fullUrl,
    method: method.toUpperCase(),
    headers: [] as { key: string; value: string }[],
  };

  if (operation.security?.length > 0) {
    for (const req of operation.security) {
      for (const sn of Object.keys(req)) {
        template.security = { id: sn };
        break;
      }
      break;
    }
  }

  if (operation.requestBody?.content) {
    for (const ct of Object.keys(operation.requestBody.content)) {
      template.headers.push({ key: 'Content-Type', value: ct });
      break;
    }
  }

  return template;
}

function createResponseTemplate(operation: any) {
  let successResponse: any = null;
  if (operation.responses) {
    for (const [code, resp] of Object.entries(operation.responses)) {
      if (code.startsWith('2') && resp) {
        successResponse = resp;
        break;
      }
    }
  }

  if (!successResponse || !successResponse.content || Object.keys(successResponse.content).length === 0) {
    return {};
  }

  const template: any = {
    prependBody:
      '# API Response Information\n\n' +
      "Below is the response from an API call. To help you understand the data, I've provided:\n\n" +
      '1. A detailed description of all fields in the response structure\n' +
      '2. The complete API response\n\n' +
      '## Response Structure\n\n',
  };

  for (const [contentType, mediaType] of Object.entries(successResponse.content) as [string, any][]) {
    if (!mediaType.schema) continue;
    template.prependBody += `> Content-Type: ${contentType}\n\n`;
    const schema = mediaType.schema;
    if (schema.type === 'array' && schema.items) {
      template.prependBody += '- **items**: Array of items (Type: array)\n';
      processSchemaProperties(template, schema.items, 'items', 1, 10);
    } else if (schema.type === 'object' && schema.properties) {
      for (const pn of Object.keys(schema.properties).sort()) {
        const pr = schema.properties[pn];
        if (!pr) continue;
        template.prependBody += `- **${pn}**: ${pr.description || ''}`;
        if (pr.type) template.prependBody += ` (Type: ${pr.type})`;
        template.prependBody += '\n';
        processSchemaProperties(template, pr, pn, 1, 10);
      }
    }
  }

  template.prependBody += '\n## Original Response\n\n';
  return template;
}

function processSchemaProperties(template: any, schema: any, path: string, depth: number, maxDepth: number) {
  if (depth > maxDepth) return;
  const indent = '  '.repeat(depth);

  if (schema.type === 'array' && schema.items) {
    const items = schema.items;
    if (items.type === 'object' && items.properties) {
      for (const pn of Object.keys(items.properties).sort()) {
        const pr = items.properties[pn];
        if (!pr) continue;
        const pp = `${path}[][${pn}]`;
        template.prependBody += `${indent}- **${pp}**: ${pr.description || ''}`;
        if (pr.type) template.prependBody += ` (Type: ${pr.type})`;
        template.prependBody += '\n';
        processSchemaProperties(template, pr, pp, depth + 1, maxDepth);
      }
    } else if (items.type) {
      template.prependBody += `${indent}- **${path}[]**: Items of type ${items.type}\n`;
    }
    return;
  }

  if (schema.type === 'object' && schema.properties) {
    for (const pn of Object.keys(schema.properties).sort()) {
      const pr = schema.properties[pn];
      if (!pr) continue;
      const pp = `${path}.${pn}`;
      template.prependBody += `${indent}- **${pp}**: ${pr.description || ''}`;
      if (pr.type) template.prependBody += ` (Type: ${pr.type})`;
      template.prependBody += '\n';
      processSchemaProperties(template, pr, pp, depth + 1, maxDepth);
    }
  }
}

/**
 * Transform extracted OpenAPI config into MCP toolSpecification format.
 * Ported from OpenApiService.js transformToolsFromConfig()
 */
export function transformToolsFromConfig(config: McpConfig) {
  const securitySchemes = config.server?.securitySchemes || [];

  const toolsMeta: Record<string, any> = {};
  for (const tool of config.tools) {
    const argsPosition: Record<string, string> = {};
    for (const arg of tool.args) {
      argsPosition[arg.name] = arg.position;
    }
    toolsMeta[tool.name] = {
      enabled: true,
      templates: {
        'json-go-template': {
          responseTemplate: tool.responseTemplate,
          requestTemplate: { ...tool.requestTemplate },
          argsPosition,
        },
      },
    };
  }

  const tools = config.tools.map((tool) => ({
    name: tool.name,
    description: tool.description,
    inputSchema: {
      type: 'object' as const,
      properties: tool.args.reduce(
        (acc, arg) => {
          acc[arg.name] = {
            type: arg.type,
            description: arg.description,
            ...(arg.properties ? { properties: arg.properties } : {}),
            ...(arg.enum ? { enum: arg.enum } : {}),
            ...(arg.items ? { items: arg.items } : {}),
          };
          return acc;
        },
        {} as Record<string, any>
      ),
      required: tool.args.filter((a) => a.required).map((a) => a.name),
    },
  }));

  // Process argsPosition -> requestTemplate mapping
  try {
    const toolArgsByName: Record<string, ToolArg[]> = {};
    for (const t of config.tools) {
      toolArgsByName[t.name] = t.args || [];
    }

    const ensureHeadersArray = (headers: any): { key: string; value: string }[] => {
      if (!headers) return [];
      if (Array.isArray(headers)) return headers;
      if (typeof headers === 'object') {
        return Object.entries(headers).map(([k, v]) => ({ key: k, value: String(v) }));
      }
      return [];
    };

    const hasHeaderKey = (headers: { key: string; value: string }[], key: string) =>
      headers.some((h) => (h.key || '').toLowerCase() === key.toLowerCase());

    const getContentType = (headers: { key: string; value: string }[]) => {
      const h = headers.find((it) => (it.key || '').toLowerCase() === 'content-type');
      return h ? h.value.toLowerCase() : '';
    };

    for (const toolName of Object.keys(toolsMeta)) {
      const meta = toolsMeta[toolName];
      const tmpl = meta?.templates?.['json-go-template'];
      if (!tmpl?.requestTemplate) continue;

      const argsPos: Record<string, string> = tmpl.argsPosition || {};
      let url: string = tmpl.requestTemplate.url || '';
      let headers = ensureHeadersArray(tmpl.requestTemplate.headers);
      let body: string | undefined = tmpl.requestTemplate.body;

      const allArgs = toolArgsByName[toolName] || [];
      const byName: Record<string, ToolArg> = {};
      for (const a of allArgs) byName[a.name] = a;

      const entries = Object.entries(argsPos);
      const pathArgs = entries.filter(([, pos]) => pos === 'path').map(([n]) => n);
      const queryArgs = entries.filter(([, pos]) => pos === 'query').map(([n]) => n);
      const headerArgs = entries.filter(([, pos]) => pos === 'header').map(([n]) => n);
      const cookieArgs = entries.filter(([, pos]) => pos === 'cookie').map(([n]) => n);
      const bodyArgs = entries.filter(([, pos]) => pos === 'body').map(([n]) => n);

      let shouldKeepArgsPosition = false;
      const totalArgsCount = entries.length;
      const allInQuery = totalArgsCount > 0 && queryArgs.length === totalArgsCount;
      const allInBody = totalArgsCount > 0 && bodyArgs.length === totalArgsCount;

      // path placeholders
      for (const name of pathArgs) {
        const re = new RegExp('\\{' + name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + '\\}', 'g');
        url = url.replace(re, `{{.args.${name}}}`);
      }

      // query params
      if (allInQuery) {
        tmpl.requestTemplate.argsToUrlParam = true;
      } else if (queryArgs.length > 0) {
        const pairs = queryArgs.map((name) => `${name}={{.args.${name}}}`);
        const connector = url.includes('?') ? '&' : '?';
        url = url + connector + pairs.join('&');
      }

      // header params
      for (const name of headerArgs) {
        if (!hasHeaderKey(headers, name)) {
          headers.push({ key: name, value: `{{.args.${name}}}` });
        }
      }

      // cookie params
      if (cookieArgs.length > 0) {
        const cookiePairs = cookieArgs.map((name) => `${name}={{.args.${name}}}`);
        const cookieValue = cookiePairs.join('; ');
        const idx = headers.findIndex((h) => (h.key || '').toLowerCase() === 'cookie');
        if (idx >= 0) {
          headers[idx].value = headers[idx].value ? `${headers[idx].value}; ${cookieValue}` : cookieValue;
        } else {
          headers.push({ key: 'Cookie', value: cookieValue });
        }
      }

      // body params
      const hasExplicit =
        body !== undefined ||
        tmpl.requestTemplate.argsToJsonBody === true ||
        tmpl.requestTemplate.argsToFormBody === true ||
        tmpl.requestTemplate.argsToUrlParam === true;

      if (bodyArgs.length > 0) {
        const ct = getContentType(headers);
        if (allInBody) {
          if (ct.includes('application/x-www-form-urlencoded') || ct.includes('multipart/form-data')) {
            tmpl.requestTemplate.argsToFormBody = true;
          } else {
            tmpl.requestTemplate.argsToJsonBody = true;
            if (!getContentType(headers) && !hasHeaderKey(headers, 'Content-Type')) {
              headers.push({ key: 'Content-Type', value: 'application/json; charset=utf-8' });
            }
          }
        } else if (!hasExplicit) {
          if (ct.includes('application/x-www-form-urlencoded')) {
            const formPairs = bodyArgs.map((name) => `${name}={{.args.${name}}}`);
            body = formPairs.join('&');
          } else {
            const hasComplex = bodyArgs.some((n) => {
              const a = byName[n];
              const t = a?.type;
              return t === 'object' || t === 'array';
            });
            if (hasComplex) {
              tmpl.requestTemplate.argsToJsonBody = true;
              shouldKeepArgsPosition = true;
              if (!hasHeaderKey(headers, 'Content-Type')) {
                headers.push({ key: 'Content-Type', value: 'application/json; charset=utf-8' });
              }
            } else {
              const jsonPairs = bodyArgs.map((name) => {
                const a = byName[name];
                const isString = a?.type === 'string';
                const valueTpl = isString ? `"{{.args.${name}}}"` : `{{.args.${name}}}`;
                return `  "${name}": ${valueTpl}`;
              });
              body = `{\n${jsonPairs.join(',\n')}\n}`;
              if (!hasHeaderKey(headers, 'Content-Type')) {
                headers.push({ key: 'Content-Type', value: 'application/json; charset=utf-8' });
              }
            }
          }
        }
      }

      tmpl.requestTemplate.url = url;
      if (headers.length > 0) tmpl.requestTemplate.headers = headers;
      if (body !== undefined) {
        tmpl.requestTemplate.body = body;
        delete tmpl.requestTemplate.argsToJsonBody;
        delete tmpl.requestTemplate.argsToUrlParam;
        delete tmpl.requestTemplate.argsToFormBody;
      } else {
        const ct2 = getContentType(headers);
        if (!allInBody && bodyArgs.length > 0 && ct2.includes('application/x-www-form-urlencoded')) {
          tmpl.requestTemplate.argsToFormBody = true;
          shouldKeepArgsPosition = true;
        }
      }
      if (!shouldKeepArgsPosition || allInQuery || allInBody) {
        delete tmpl.argsPosition;
      }
    }
  } catch (e) {
    console.warn('argsPosition to requestTemplate transform failed:', e);
  }

  return { tools, toolsMeta, securitySchemes };
}
