import { OpenAPIV3 } from 'openapi-types';
import { Tool } from '../types/index.js';
import { getServersInfo } from './mcpService.js';
import config, { getNameSeparator } from '../config/index.js';
import { getSystemConfigDao } from '../dao/index.js';

/**
 * Service for generating OpenAPI 3.x specifications from MCP tools
 * This enables integration with OpenWebUI and other OpenAPI-compatible systems
 */

export interface OpenAPIGenerationOptions {
  title?: string;
  description?: string;
  version?: string;
  serverUrl?: string;
  includeDisabledTools?: boolean;
  groupFilter?: string;
  serverFilter?: string[];
}

/**
 * Convert MCP tool input schema to OpenAPI parameter or request body schema
 */
function convertToolSchemaToOpenAPI(tool: Tool): {
  parameters?: OpenAPIV3.ParameterObject[];
  requestBody?: OpenAPIV3.RequestBodyObject;
} {
  const schema = tool.inputSchema as any;

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

  // If schema has properties, convert them to parameters or request body
  if (schema.properties && typeof schema.properties === 'object') {
    const properties = schema.properties;
    const required = Array.isArray(schema.required) ? schema.required : [];

    // For simple tools with only primitive parameters, use query parameters
    const hasComplexTypes = Object.values(properties).some(
      (prop: any) =>
        prop.type === 'object' ||
        prop.type === 'array' ||
        prop.type === 'string',
    );

    if (!hasComplexTypes && Object.keys(properties).length <= 10) {
      // Use query parameters for simple tools
      const parameters: OpenAPIV3.ParameterObject[] = Object.entries(properties).map(
        ([name, prop]: [string, any]) => ({
          name,
          in: 'query',
          required: required.includes(name),
          description: prop.description || `Parameter ${name}`,
          schema: {
            type: prop.type || 'string',
            ...(prop.enum && { enum: prop.enum }),
            ...(prop.default !== undefined && { default: prop.default }),
            ...(prop.format && { format: prop.format }),
          },
        }),
      );

      return { parameters };
    } else {
      // Use request body for complex tools
      const requestBody: OpenAPIV3.RequestBodyObject = {
        required: required.length > 0,
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties,
              ...(required.length > 0 && { required }),
            },
          },
        },
      };

      return { requestBody };
    }
  }

  return {};
}

/**
 * Generate OpenAPI operation from MCP tool
 */
function generateOperationFromTool(tool: Tool, serverName: string): OpenAPIV3.OperationObject {
  const { parameters, requestBody } = convertToolSchemaToOpenAPI(tool);
  const operation: OpenAPIV3.OperationObject = {
    summary: tool.description || `Execute ${tool.name} tool`,
    description: tool.description || `Execute the ${tool.name} tool from ${serverName} server`,
    operationId: `${tool.name}`,
    tags: [serverName],
    ...(parameters && parameters.length > 0 && { parameters }),
    ...(requestBody && { requestBody }),
    responses: {
      '200': {
        description: 'Successful tool execution',
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties: {
                content: {
                  type: 'array',
                  items: {
                    type: 'object',
                    properties: {
                      type: { type: 'string' },
                      text: { type: 'string' },
                    },
                  },
                },
                isError: { type: 'boolean' },
              },
            },
          },
        },
      },
      '400': {
        description: 'Bad request - invalid parameters',
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties: {
                error: { type: 'string' },
                message: { type: 'string' },
              },
            },
          },
        },
      },
      '500': {
        description: 'Internal server error',
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties: {
                error: { type: 'string' },
                message: { type: 'string' },
              },
            },
          },
        },
      },
    },
  };

  return operation;
}

/**
 * Generate OpenAPI specification from MCP tools
 */
export async function generateOpenAPISpec(
  options: OpenAPIGenerationOptions = {},
): Promise<OpenAPIV3.Document> {
  const serverInfos = await getServersInfo();

  // Filter servers based on options
  let filteredServers = serverInfos.filter(
    (server) =>
      server.status === 'connected' &&
      (!options.serverFilter || options.serverFilter.includes(server.name)),
  );

  // Apply group filter if specified
  const groupConfig: Map<string, string[] | 'all'> = new Map();
  if (options.groupFilter) {
    const { getGroupByIdOrName } = await import('./groupService.js');
    const group = await getGroupByIdOrName(options.groupFilter);
    if (group) {
      // Extract server names and their tool configurations from group
      const groupServerNames: string[] = [];
      for (const server of group.servers) {
        if (typeof server === 'string') {
          groupServerNames.push(server);
          groupConfig.set(server, 'all');
        } else {
          groupServerNames.push(server.name);
          groupConfig.set(server.name, server.tools || 'all');
        }
      }
      // Filter to only servers in the group
      filteredServers = filteredServers.filter((server) => groupServerNames.includes(server.name));
    } else {
      // Group not found, return empty specification
      filteredServers = [];
    }
  }

  // Collect all tools from filtered servers
  const allTools: Array<{ tool: Tool; serverName: string }> = [];

  for (const serverInfo of filteredServers) {
    const tools = options.includeDisabledTools
      ? serverInfo.tools
      : serverInfo.tools.filter((tool) => tool.enabled !== false);

    // Apply group-specific tool filtering if group filter is specified
    let filteredTools = tools;
    if (options.groupFilter && groupConfig.has(serverInfo.name)) {
      const allowedTools = groupConfig.get(serverInfo.name);
      if (allowedTools !== 'all') {
        // Filter tools to only include those specified in the group configuration
        const separator = getNameSeparator();
        filteredTools = tools.filter(
          (tool) =>
            Array.isArray(allowedTools) &&
            allowedTools.includes(tool.name.replace(serverInfo.name + separator, '')),
        );
      }
    }

    for (const tool of filteredTools) {
      allTools.push({ tool, serverName: serverInfo.name });
    }
  }

  // Generate paths from tools
  const paths: OpenAPIV3.PathsObject = {};
  const separator = getNameSeparator();

  for (const { tool, serverName } of allTools) {
    const operation = generateOperationFromTool(tool, serverName);
    const { requestBody } = convertToolSchemaToOpenAPI(tool);

    // Extract the tool name without server prefix
    // Tool names are in format: serverName + separator + toolName
    const prefix = `${serverName}${separator}`;
    const toolNameOnly = tool.name.startsWith(prefix)
      ? tool.name.substring(prefix.length)
      : tool.name;

    // Create path for the tool with URL-encoded server and tool names
    // This handles cases where names contain slashes (e.g., "com.atlassian/atlassian-mcp-server")
    const pathName = `/tools/${encodeURIComponent(serverName)}/${encodeURIComponent(toolNameOnly)}`;
    const method = requestBody ? 'post' : 'get';

    if (!paths[pathName]) {
      paths[pathName] = {};
    }

    paths[pathName][method] = operation;
  }

  const systemConfigDao = getSystemConfigDao();
  const systemConfig = await systemConfigDao.get();
  // Get server URL
  const baseUrl =
    options.serverUrl || systemConfig?.install?.baseUrl || `http://localhost:${config.port}`;
  const serverUrl = `${baseUrl}${config.basePath}/api`;

  // Generate OpenAPI document
  const openApiDoc: OpenAPIV3.Document = {
    openapi: '3.0.3',
    info: {
      title: options.title || 'MCPHub API',
      description:
        options.description ||
        'OpenAPI specification for MCP tools managed by MCPHub. This enables integration with OpenWebUI and other OpenAPI-compatible systems.',
      version: options.version || '1.0.0',
      contact: {
        name: 'MCPHub',
        url: 'https://github.com/samanhappy/mcphub',
      },
      license: {
        name: 'ISC',
        url: 'https://github.com/samanhappy/mcphub/blob/main/LICENSE',
      },
    },
    servers: [
      {
        url: serverUrl,
        description: 'MCPHub API Server',
      },
    ],
    paths,
    components: {
      schemas: {
        ToolResponse: {
          type: 'object',
          properties: {
            content: {
              type: 'array',
              items: {
                type: 'object',
                properties: {
                  type: { type: 'string' },
                  text: { type: 'string' },
                },
              },
            },
            isError: { type: 'boolean' },
          },
        },
        ErrorResponse: {
          type: 'object',
          properties: {
            error: { type: 'string' },
            message: { type: 'string' },
          },
        },
      },
      securitySchemes: {
        bearerAuth: {
          type: 'http',
          scheme: 'bearer',
          bearerFormat: 'JWT',
        },
      },
    },
    security: [
      {
        bearerAuth: [],
      },
    ],
    tags: filteredServers.map((server) => ({
      name: server.name,
      description: `Tools from ${server.name} server`,
    })),
  };

  return openApiDoc;
}

/**
 * Get available server names for filtering
 */
export async function getAvailableServers(): Promise<string[]> {
  const serverInfos = await getServersInfo();
  return serverInfos.filter((server) => server.status === 'connected').map((server) => server.name);
}

/**
 * Get statistics about available tools
 */
export async function getToolStats(): Promise<{
  totalServers: number;
  totalTools: number;
  serverBreakdown: Array<{ name: string; toolCount: number; status: string }>;
}> {
  const serverInfos = await getServersInfo();

  const serverBreakdown = serverInfos.map((server) => ({
    name: server.name,
    toolCount: server.tools.length,
    status: server.status,
  }));

  const totalTools = serverInfos
    .filter((server) => server.status === 'connected')
    .reduce((sum, server) => sum + server.tools.length, 0);

  return {
    totalServers: serverInfos.filter((server) => server.status === 'connected').length,
    totalTools,
    serverBreakdown,
  };
}
