/**
 * Extract tools from OpenAPI document
 * @returns {object} MCP config object
 * @param openapi
 */
export default function extractToolsFromOpenAPI(openapi) {
  const mcpConfig = {
    server: {
      name: 'openapi-server',
      securitySchemes: [],
    },
    tools: [],
  };

  // Process security schemes
  if (openapi.components && openapi.components.securitySchemes) {
    const { securitySchemes } = openapi.components;
    for (const name in securitySchemes) {
      if (Object.prototype.hasOwnProperty.call(securitySchemes, name)) {
        const scheme = securitySchemes[name];
        mcpConfig.server.securitySchemes.push({
          id: name,
          type: scheme.type,
          scheme: scheme.scheme,
          in: scheme.in,
          name: scheme.name,
        });
      }
    }
    // Sort security schemes by ID for consistent output
    mcpConfig.server.securitySchemes.sort((a, b) => a.id.localeCompare(b.id));
  }

  // Process paths and operations
  if (openapi.paths) {
    for (const path in openapi.paths) {
      if (Object.prototype.hasOwnProperty.call(openapi.paths, path)) {
        const pathItem = openapi.paths[path];
        const operations = getOperations(pathItem);
        for (const method in operations) {
          if (Object.prototype.hasOwnProperty.call(operations, method)) {
            const operation = operations[method];
            const tool = convertOperation(path, method, operation, openapi.servers);
            // Create response template
            tool.responseTemplate = createResponseTemplate(operation);
            mcpConfig.tools.push(tool);
          }
        }
      }
    }
  }
  // Sort tools by name for consistent output
  mcpConfig.tools.sort((a, b) => a.name.localeCompare(b.name));

  return mcpConfig;
}

function getOperations(pathItem) {
  const operations = {};

  if (pathItem.get) operations.get = pathItem.get;
  if (pathItem.post) operations.post = pathItem.post;
  if (pathItem.put) operations.put = pathItem.put;
  if (pathItem.delete) operations.delete = pathItem.delete;
  if (pathItem.options) operations.options = pathItem.options;
  if (pathItem.head) operations.head = pathItem.head;
  if (pathItem.patch) operations.patch = pathItem.patch;
  if (pathItem.trace) operations.trace = pathItem.trace;

  return operations;
}

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

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

  // Convert parameters to arguments
  // Process operation-level security requirements
  if (operation.security && operation.security.length > 0) {
    // Take the first security requirement
    const securityRequirement = operation.security[0];
    // Take the first scheme from that requirement
    const schemeNames = Object.keys(securityRequirement);
    if (schemeNames.length > 0) {
      tool.requestTemplate.security = {
        id: schemeNames[0],
      };
    }
  }

  // Convert parameters to arguments
  if (operation.parameters) {
    tool.args = operation.parameters.map(param => {
      const arg = {
        name: param.name,
        description: param.description || '',
        required: param.required || false,
        position: param.in,
      };

      // Set the type based on the schema
      if (param.schema) {
        const { schema } = param;
        // Set the type based on the schema type
        arg.type = schema.type;

        // Handle enum values
        if (schema.enum && schema.enum.length > 0) {
          arg.enum = schema.enum;
        }

        // Handle array type
        if (schema.type === 'array' && schema.items) {
          arg.items = {
            type: schema.items.type,
          };
        }

        // Handle object type
        if (schema.type === 'object' && schema.properties) {
          arg.properties = {};
          for (const propName in schema.properties) {
            if (Object.prototype.hasOwnProperty.call(schema.properties, propName)) {
              const prop = schema.properties[propName];
              if (prop) {
                arg.properties[propName] = {
                  type: prop.type,
                };
                if (prop.description) {
                  arg.properties[propName].description = prop.description;
                }
              }
            }
          }
        }
      }

      return arg;
    });
  }

  // Convert request body to arguments
  if (operation.requestBody && operation.requestBody.content) {
    for (const contentType in operation.requestBody.content) {
      if (Object.prototype.hasOwnProperty.call(operation.requestBody.content, contentType)) {
        const mediaType = operation.requestBody.content[contentType];
        if (mediaType.schema) {
          const { schema } = mediaType;

          // For JSON and form content types, convert the schema to arguments
          if (
            contentType.includes('application/json') ||
            contentType.includes('application/x-www-form-urlencoded')
          ) {
            // For object type, convert each property to an argument
            if (schema.type === 'object' && schema.properties) {
              for (const propName in schema.properties) {
                if (Object.prototype.hasOwnProperty.call(schema.properties, propName)) {
                  const prop = schema.properties[propName];
                  if (!prop) {
                    continue;
                  }

                  const arg = {
                    name: propName,
                    description: prop.description || '',
                    type: prop.type,
                    required: schema.required && schema.required.includes(propName),
                    position: 'body',
                  };

                  // Handle enum values
                  if (prop.enum && prop.enum.length > 0) {
                    arg.enum = prop.enum;
                  }

                  // Handle array type
                  if (prop.type === 'array' && prop.items) {
                    arg.items = {
                      type: prop.items.type,
                      description: prop.items.description || '',
                    };

                    if (prop.items.minItems > 0) {
                      arg.items.minItems = prop.items.minItems;
                    }

                    if (prop.items.type === 'object' && prop.items.properties) {
                      arg.items.properties = prop.items.properties;
                    }
                  }

                  // Handle object type
                  if (prop.type === 'object' && prop.properties) {
                    arg.properties = {};
                    for (const subPropName in prop.properties) {
                      if (Object.prototype.hasOwnProperty.call(prop.properties, subPropName)) {
                        const subProp = prop.properties[subPropName];
                        if (subProp) {
                          const subPropObj = {
                            type: subProp.type,
                            description: subProp.description || '',
                          };

                          if (subProp.default !== undefined) {
                            subPropObj.default = subProp.default;
                          }

                          if (subProp.enum) {
                            subPropObj.enum = subProp.enum;
                          }

                          arg.properties[subPropName] = subPropObj;
                        }
                      }
                    }
                  }

                  // Handle allOf
                  if (!prop.type && prop.allOf && prop.allOf.length === 1) {
                    arg.type = 'object';
                    arg.properties = allOfHandle(prop.allOf[0]);
                  }

                  tool.args.push(arg);
                }
              }
            }
          }
        }
        break; // Only use the first content type
      }
    }
  }

  // Sort arguments by name for consistent output
  tool.args.sort((a, b) => a.name.localeCompare(b.name));

  return tool;
}

function allOfHandle(schema) {
  const properties = {};

  if (schema.type === 'object' && schema.properties) {
    for (const propName in schema.properties) {
      if (Object.prototype.hasOwnProperty.call(schema.properties, propName)) {
        const prop = schema.properties[propName];
        if (prop) {
          properties[propName] = {
            type: prop.type,
          };

          if (prop.description) {
            properties[propName].description = prop.description;
          }

          // Handle nested allOf
          if (!prop.type && prop.allOf && prop.allOf.length === 1) {
            properties[propName].type = 'object';
            properties[propName].properties = allOfHandle(prop.allOf[0]);
          }
        }
      }
    }
  }

  return properties;
}

function createRequestTemplate(path, method, operation, servers) {
  let serverURL = servers && servers.length > 0 ? servers[0].url : '';
  let fullUrl = '';
  if (typeof serverURL === 'string') {
    serverURL = serverURL.trim();
    const m = serverURL.match(/^(https?:\/\/)(.*)$/i);
    if (m) {
      serverURL = m[1] + m[2].replace(/\/{2,}/g, '/');
    } else {
      serverURL = serverURL.replace(/\/{2,}/g, '/');
    }
    // Trim trailing slashes to avoid double slashes when concatenating with path
    serverURL = serverURL.replace(/\/+$/, '');
  }
  // Ensure path starts with a single slash and collapse duplicates
  const normalizedPath =
    typeof path === 'string' ? `/${path}`.replace(/\/{2,}/g, '/').replace(/^\/+/, '/') : '';
  fullUrl = serverURL + normalizedPath;

  const template = {
    url: fullUrl,
    method: method.toUpperCase(),
    headers: [],
  };

  // Process operation-level security requirements
  if (operation.security && operation.security.length > 0) {
    for (const securityRequirement of operation.security) {
      for (const schemeName in securityRequirement) {
        if (Object.prototype.hasOwnProperty.call(securityRequirement, schemeName)) {
          template.security = { id: schemeName };
          break;
        }
      }
    }
  }

  // Add Content-Type header based on request body content type
  if (operation.requestBody) {
    for (const contentType in operation.requestBody.content) {
      if (Object.prototype.hasOwnProperty.call(operation.requestBody.content, contentType)) {
        template.headers.push({
          key: 'Content-Type',
          value: contentType,
        });
        break; // Only use the first content type
      }
    }
  }

  return template;
}

/**
 * Create a response template from an OpenAPI operation
 * @param {object} operation OpenAPI operation object
 * @returns {object} Response template object
 */
function createResponseTemplate(operation) {
  // Find the success response (200, 201, etc.)
  let successResponse = null;
  if (operation.responses) {
    for (const [code, responseRef] of Object.entries(operation.responses)) {
      if (code.startsWith('2') && responseRef) {
        successResponse = responseRef;
        break;
      }
    }
  }

  // If there's no success response, don't add a response template
  if (!successResponse || Object.keys(successResponse.content || {}).length === 0) {
    return {};
  }

  // Create the response template
  const template = {
    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',
  };

  // Process each content type
  for (const [contentType, mediaType] of Object.entries(successResponse.content)) {
    if (!mediaType.schema) {
      continue;
    }

    template.prependBody += `> Content-Type: ${contentType}\n\n`;
    const { schema } = mediaType;

    // Generate field descriptions using recursive function
    if (schema.type === 'array' && schema.items) {
      // Handle array type
      template.prependBody += '- **items**: Array of items (Type: array)\n';
      // Process array items recursively
      processSchemaProperties(template, schema.items, 'items', 1, 10);
    } else if (schema.type === 'object' && Object.keys(schema.properties || {}).length > 0) {
      // Get property names and sort them alphabetically for consistent output
      const propNames = Object.keys(schema.properties).sort();

      // Process properties in alphabetical order
      for (const propName of propNames) {
        const propRef = schema.properties[propName];
        if (!propRef) {
          continue;
        }

        // Write the property description
        template.prependBody += `- **${propName}**: ${propRef.description || ''}`;
        if (propRef.type) {
          template.prependBody += ` (Type: ${propRef.type})`;
        }
        template.prependBody += '\n';

        // Process nested properties recursively
        processSchemaProperties(template, propRef, propName, 1, 10);
      }
    }
  }

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

/**
 * Process schema properties recursively
 * @param {object} template Response template object
 * @param {object} schema OpenAPI schema object
 * @param {string} path Current property path
 * @param {number} depth Current recursion depth
 * @param {number} maxDepth Maximum recursion depth
 */
function processSchemaProperties(template, schema, path, depth, maxDepth) {
  if (depth > maxDepth) {
    return; // Stop recursion if max depth is reached
  }

  // Calculate indentation based on depth
  const indent = '  '.repeat(depth);

  // Handle array type
  if (schema.type === 'array' && schema.items) {
    const arrayItemSchema = schema.items;

    // If array items are objects, describe their properties
    if (
      arrayItemSchema.type === 'object' &&
      Object.keys(arrayItemSchema.properties || {}).length > 0
    ) {
      // Get property names for consistent output
      const propNames = Object.keys(arrayItemSchema.properties).sort();

      // Process each property
      for (const propName of propNames) {
        const propRef = arrayItemSchema.properties[propName];
        if (!propRef) {
          continue;
        }

        // Write the property description
        const propPath = `${path}[][${propName}]`;
        template.prependBody += `${indent}- **${propPath}**: ${propRef.description || ''}`;
        if (propRef.type) {
          template.prependBody += ` (Type: ${propRef.type})`;
        }
        template.prependBody += '\n';

        // Process nested properties recursively
        processSchemaProperties(template, propRef, propPath, depth + 1, maxDepth);
      }
    } else if (arrayItemSchema.type) {
      // If array items are not objects, just describe the array item type
      template.prependBody += `${indent}- **${path}[]**: Items of type ${arrayItemSchema.type}\n`;
    }
    return;
  }

  // Handle object type
  if (schema.type === 'object' && Object.keys(schema.properties || {}).length > 0) {
    // Get property names for consistent output
    const propNames = Object.keys(schema.properties).sort();

    // Process each property
    for (const propName of propNames) {
      const propRef = schema.properties[propName];
      if (!propRef) {
        continue;
      }

      // Write the property description
      const propPath = `${path}.${propName}`;
      template.prependBody += `${indent}- **${propPath}**: ${propRef.description || ''}`;
      if (propRef.type) {
        template.prependBody += ` (Type: ${propRef.type})`;
      }
      template.prependBody += '\n';

      // Process nested properties recursively
      processSchemaProperties(template, propRef, propPath, depth + 1, maxDepth);
    }
  }
}
