import { METHOD_METADATA, PATH_METADATA } from "@nestjs/common/constants";

import { INestiaProject } from "../structures/INestiaProject";
import { IOperationMetadata } from "../structures/IOperationMetadata";
import { IReflectController } from "../structures/IReflectController";
import { IReflectImport } from "../structures/IReflectImport";
import { IReflectMcpOperation } from "../structures/IReflectMcpOperation";
import { IReflectMcpOperationParameter } from "../structures/IReflectMcpOperationParameter";
import { ImportAnalyzer } from "./ImportAnalyzer";

export namespace ReflectMcpOperationAnalyzer {
  export interface IProps {
    project: Omit<INestiaProject, "config">;
    controller: IReflectController;
    function: Function;
    name: string;
    metadata: IOperationMetadata;
  }

  export const analyze = (ctx: IProps): IReflectMcpOperation | null => {
    const route:
      | {
          name: string;
          title?: string;
          description?: string;
          inputSchema: object;
          outputSchema?: object;
          annotations?: IReflectMcpOperation.IAnnotations;
        }
      | undefined = Reflect.getMetadata("nestia/McpRoute", ctx.function);
    if (route === undefined) return null;

    const errors: string[] = [];
    const hasHttpRoute: boolean =
      Reflect.getMetadata(PATH_METADATA, ctx.function) !== undefined ||
      Reflect.getMetadata(METHOD_METADATA, ctx.function) !== undefined;
    const hasWebSocketRoute: boolean =
      Reflect.getMetadata("nestia/WebSocketRoute", ctx.function) !== undefined;

    if (hasHttpRoute || hasWebSocketRoute)
      errors.push(
        "@McpRoute must not be combined with HTTP or WebSocket route decorators on the same method.",
      );

    const preconfigured: IReflectMcpOperationParameter.IPreconfigured[] = (
      (Reflect.getMetadata(
        "nestia/McpRoute/Parameters",
        ctx.controller.class.prototype,
        ctx.name,
      ) ?? []) as IReflectMcpOperationParameter.IPreconfigured[]
    ).sort((a, b) => a.index - b.index);

    if (preconfigured.length > 1)
      errors.push(
        "@McpRoute tools may declare at most one @McpRoute.Params() parameter.",
      );
    if (ctx.function.length > 1)
      errors.push(
        "@McpRoute tools must have 0 or 1 parameters (the MCP arguments object).",
      );

    const imports: IReflectImport[] = [];
    const parameters: IReflectMcpOperationParameter[] = preconfigured
      .map((p) => {
        const matched: IOperationMetadata.IParameter | undefined =
          ctx.metadata.parameters.find(
            (m: IOperationMetadata.IParameter) => p.index === m.index,
          );
        if (matched === undefined) {
          errors.push(
            `Unable to find parameter type of the ${p.index} (th) argument.`,
          );
          return null;
        }
        if (matched.type === null) {
          errors.push(
            `Failed to analyze the parameter type of ${JSON.stringify(matched.name)}.`,
          );
          return null;
        }
        imports.push(...matched.imports);
        return {
          category: "params" as const,
          name: matched.name,
          index: p.index,
          type: matched.type,
          imports: matched.imports,
          description: matched.description,
          jsDocTags: matched.jsDocTags,
        };
      })
      .filter((p): p is IReflectMcpOperationParameter => !!p);

    if (ctx.metadata.success?.imports?.length)
      imports.push(...ctx.metadata.success.imports);

    if (errors.length) {
      ctx.project.errors.push({
        file: ctx.controller.file,
        class: ctx.controller.class.name,
        function: ctx.function.name,
        from: ctx.name,
        contents: errors,
      });
      return null;
    }
    return {
      protocol: "mcp",
      name: ctx.name,
      toolName: route.name,
      title: route.title ?? null,
      toolDescription: route.description ?? null,
      inputSchema: route.inputSchema,
      outputSchema: route.outputSchema ?? null,
      annotations: route.annotations ?? null,
      function: ctx.function,
      parameters,
      returnType: ctx.metadata.success?.type ?? null,
      imports: ImportAnalyzer.merge(imports),
      description: ctx.metadata.description ?? null,
      jsDocTags: ctx.metadata.jsDocTags,
    };
  };
}
