import { CreateProjectSchema, PermissionKey, type ProjectStatus, UpdateProjectSchema } from "@capakraken/shared";
import { fmtEur } from "../../lib/format-utils.js";
import type { TRPCContext } from "../../trpc.js";
import type { ToolContext, ToolDef, ToolExecutor } from "./shared.js";

type AssistantToolErrorResult = { error: string };

type ResolvedProject = {
  id: string;
  name: string;
  shortCode: string;
};

type ResolvedReference = {
  id: string;
};

type ProjectSummaryRecord = {
  id: string;
  name: string;
  shortCode: string;
};

type ProjectRecord = ProjectSummaryRecord & {
  status?: string;
};

type ResponsiblePersonResolution =
  | {
      status: "resolved";
      displayName: string;
    }
  | {
      status: "ambiguous" | "missing";
      message: string;
    };

type ProjectToolsDeps = {
  assertPermission: (ctx: ToolContext, perm: PermissionKey) => void;
  createProjectCaller: (ctx: TRPCContext) => {
    searchSummariesDetail: (params: any) => Promise<unknown>;
    getByIdentifierDetail: (params: { identifier: string }) => Promise<unknown>;
    update: (params: any) => Promise<ProjectSummaryRecord>;
    create: (params: any) => Promise<ProjectRecord>;
    delete: (params: { id: string }) => Promise<unknown>;
    generateCover: (params: {
      projectId: string;
      prompt?: string;
    }) => Promise<{ coverImageUrl: string }>;
    removeCover: (params: { projectId: string }) => Promise<unknown>;
  };
  createBlueprintCaller: (ctx: TRPCContext) => {
    resolveByIdentifier: (params: {
      identifier: string;
    }) => Promise<ResolvedReference>;
  };
  createClientCaller: (ctx: TRPCContext) => {
    resolveByIdentifier: (params: {
      identifier: string;
    }) => Promise<ResolvedReference>;
  };
  createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
  resolveProjectIdentifier: (
    ctx: ToolContext,
    identifier: string,
  ) => Promise<ResolvedProject | AssistantToolErrorResult>;
  resolveResponsiblePerson: (
    name: string,
    ctx: ToolContext,
  ) => Promise<{ displayName: string } | { error: string }>;
  resolveEntityOrAssistantError: <T>(
    resolve: () => Promise<T>,
    notFoundMessage: string,
  ) => Promise<T | AssistantToolErrorResult>;
  toAssistantNotFoundError: (
    error: unknown,
    message: string,
  ) => AssistantToolErrorResult | null;
  toAssistantProjectMutationError: (
    error: unknown,
  ) => AssistantToolErrorResult | null;
  toAssistantProjectCreationError: (
    error: unknown,
    shortCode: string,
  ) => AssistantToolErrorResult | null;
  toAssistantProjectNotFoundError: (
    error: unknown,
    identifier: string,
  ) => AssistantToolErrorResult | null;
};

async function resolveOptionalReferenceId(
  identifier: string | undefined,
  notFoundMessage: string,
  resolveReference: () => Promise<ResolvedReference>,
  deps: ProjectToolsDeps,
): Promise<string | AssistantToolErrorResult | undefined> {
  if (!identifier) {
    return undefined;
  }

  const resolved = await deps.resolveEntityOrAssistantError(
    resolveReference,
    notFoundMessage,
  );
  if ("error" in resolved) {
    return resolved;
  }
  return resolved.id;
}

export const projectReadToolDefinitions: ToolDef[] = [
  {
    type: "function",
    function: {
      name: "search_projects",
      description: "Search for projects by name, short code, status, or client.",
      parameters: {
        type: "object",
        properties: {
          query: { type: "string", description: "Search term (matches name, shortCode)" },
          status: { type: "string", description: "Filter by status: DRAFT, ACTIVE, ON_HOLD, COMPLETED, CANCELLED" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_project",
      description: "Get detailed information about a single project by ID or short code, including top allocations.",
      parameters: {
        type: "object",
        properties: {
          identifier: { type: "string", description: "Project ID or short code (e.g. Z033T593)" },
        },
        required: ["identifier"],
      },
    },
  },
];

export const projectMutationToolDefinitions: ToolDef[] = [
  {
    type: "function",
    function: {
      name: "update_project",
      description: "Update a project's details. Requires manageProjects permission. Always confirm with the user before calling this.",
      parameters: {
        type: "object",
        properties: {
          id: { type: "string", description: "Project ID, short code, or project name" },
          name: { type: "string", description: "New project name" },
          budgetCents: { type: "integer", description: "New budget in cents (e.g. 10000000 = 100,000 EUR)" },
          winProbability: { type: "integer", description: "Win probability 0-100" },
          status: { type: "string", description: "New status: DRAFT, ACTIVE, ON_HOLD, COMPLETED, CANCELLED" },
          responsiblePerson: { type: "string", description: "Name of the responsible person. Must match an existing resource's display name (case-insensitive search)." },
        },
        required: ["id"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "create_project",
      description: "Create a new project. Requires manageProjects permission. Always confirm with the user before calling this. The project is created in DRAFT status by default.",
      parameters: {
        type: "object",
        properties: {
          shortCode: { type: "string", description: "Unique project code, uppercase alphanumeric with hyphens/underscores (e.g. 'PROJ-001')" },
          name: { type: "string", description: "Project name" },
          orderType: { type: "string", description: "Order type: BD, CHARGEABLE, INTERNAL, OVERHEAD" },
          allocationType: { type: "string", description: "Allocation type: INT or EXT. Default: INT" },
          budgetCents: { type: "integer", description: "Budget in cents (e.g. 10000000 = 100,000 EUR)" },
          startDate: { type: "string", description: "Start date (YYYY-MM-DD)" },
          endDate: { type: "string", description: "End date (YYYY-MM-DD)" },
          winProbability: { type: "integer", description: "Win probability 0-100. Default: 100" },
          status: { type: "string", description: "Initial status: DRAFT, ACTIVE, ON_HOLD. Default: DRAFT" },
          responsiblePerson: { type: "string", description: "Name of the responsible person. Must match an existing resource's display name (case-insensitive search)." },
          color: { type: "string", description: "Hex color (e.g. '#3b82f6')" },
          blueprintName: { type: "string", description: "Blueprint name to look up and attach (partial match)" },
          clientName: { type: "string", description: "Client name to look up and attach (partial match)" },
        },
        required: ["shortCode", "name", "orderType", "budgetCents", "startDate", "endDate", "responsiblePerson"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "delete_project",
      description: "Delete a project. Only DRAFT projects can be deleted. Requires manageProjects permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "generate_project_cover",
      description: "Generate an AI cover art image for a project. Uses the configured image provider (DALL-E or Google Gemini). The image will be stored as the project's cover. Requires manageProjects permission.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID" },
          prompt: { type: "string", description: "Optional custom prompt for the AI image generation (e.g. 'futuristic car in neon cityscape'). If not provided, a default automotive/CGI prompt is used based on the project name." },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "remove_project_cover",
      description: "Remove the cover art image from a project. Requires manageProjects permission.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID" },
        },
        required: ["projectId"],
      },
    },
  },
];

export function createProjectExecutors(
  deps: ProjectToolsDeps,
): Record<string, ToolExecutor> {
  return {
    async search_projects(
      params: { query?: string; status?: string; limit?: number },
      ctx: ToolContext,
    ) {
      const caller = deps.createProjectCaller(deps.createScopedCallerContext(ctx));
      return caller.searchSummariesDetail({
        ...(params.query !== undefined ? { search: params.query } : {}),
        ...(params.status !== undefined ? { status: params.status as ProjectStatus } : {}),
        limit: Math.min(params.limit ?? 20, 50),
      });
    },

    async get_project(params: { identifier: string }, ctx: ToolContext) {
      const caller = deps.createProjectCaller(deps.createScopedCallerContext(ctx));
      try {
        return await caller.getByIdentifierDetail({ identifier: params.identifier });
      } catch (error) {
        const mapped = deps.toAssistantNotFoundError(
          error,
          `Project not found: ${params.identifier}`,
        );
        if (mapped) {
          return mapped;
        }
        throw error;
      }
    },

    async update_project(
      params: {
        id: string;
        name?: string;
        budgetCents?: number;
        winProbability?: number;
        status?: string;
        responsiblePerson?: string;
      },
      ctx: ToolContext,
    ) {
      deps.assertPermission(ctx, PermissionKey.MANAGE_PROJECTS);
      const project = await deps.resolveProjectIdentifier(ctx, params.id);
      if ("error" in project) {
        return project;
      }

      const data: Record<string, unknown> = {};
      if (params.name !== undefined) data.name = params.name;
      if (params.budgetCents !== undefined) data.budgetCents = params.budgetCents;
      if (params.winProbability !== undefined) data.winProbability = params.winProbability;
      if (params.status !== undefined) data.status = params.status;

      if (params.responsiblePerson !== undefined) {
        const result = await deps.resolveResponsiblePerson(params.responsiblePerson, ctx);
        if ("error" in result) {
          return { error: result.error };
        }
        data.responsiblePerson = result.displayName;
      }

      const parsedData = UpdateProjectSchema.parse(data);
      const updatedFields = Object.keys(parsedData);
      if (updatedFields.length === 0) {
        return { error: "No fields to update" };
      }

      const caller = deps.createProjectCaller(deps.createScopedCallerContext(ctx));
      let updated;
      try {
        updated = await caller.update({ id: project.id, data: parsedData });
      } catch (error) {
        const mapped = deps.toAssistantProjectMutationError(error);
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["project"],
        success: true,
        message: `Updated project ${updated.name} (${updated.shortCode})`,
        updatedFields,
      };
    },

    async create_project(
      params: {
        shortCode: string;
        name: string;
        orderType: string;
        allocationType?: string;
        budgetCents: number;
        startDate: string;
        endDate: string;
        winProbability?: number;
        status?: string;
        responsiblePerson?: string;
        color?: string;
        blueprintName?: string;
        clientName?: string;
      },
      ctx: ToolContext,
    ) {
      deps.assertPermission(ctx, PermissionKey.MANAGE_PROJECTS);
      if (!params.responsiblePerson?.trim()) {
        return { error: "responsiblePerson is required to create a project." };
      }

      const responsible = await deps.resolveResponsiblePerson(params.responsiblePerson, ctx);
      if ("error" in responsible) {
        return { error: responsible.error };
      }

      const scopedContext = deps.createScopedCallerContext(ctx);
      const blueprintCaller = deps.createBlueprintCaller(scopedContext);
      const clientCaller = deps.createClientCaller(scopedContext);

      const blueprintId = await resolveOptionalReferenceId(
        params.blueprintName,
        `Blueprint not found: "${params.blueprintName}"`,
        () => blueprintCaller.resolveByIdentifier({ identifier: params.blueprintName! }),
        deps,
      );
      if (blueprintId && typeof blueprintId !== "string") {
        return blueprintId;
      }

      const clientId = await resolveOptionalReferenceId(
        params.clientName,
        `Client not found: "${params.clientName}"`,
        () => clientCaller.resolveByIdentifier({ identifier: params.clientName! }),
        deps,
      );
      if (clientId && typeof clientId !== "string") {
        return clientId;
      }

      const input = CreateProjectSchema.parse({
        shortCode: params.shortCode,
        name: params.name,
        orderType: params.orderType,
        allocationType: params.allocationType ?? "INT",
        budgetCents: params.budgetCents,
        startDate: params.startDate,
        endDate: params.endDate,
        winProbability: params.winProbability ?? 100,
        status: params.status ?? "DRAFT",
        responsiblePerson: responsible.displayName,
        ...(params.color ? { color: params.color } : {}),
        ...(typeof blueprintId === "string" ? { blueprintId } : {}),
        ...(typeof clientId === "string" ? { clientId } : {}),
        staffingReqs: [],
        dynamicFields: {},
      });

      const caller = deps.createProjectCaller(scopedContext);
      let project;
      try {
        project = await caller.create(input);
      } catch (error) {
        const mapped = deps.toAssistantProjectCreationError(error, input.shortCode);
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["project"],
        success: true,
        message: `Created project: ${project.name} (${project.shortCode}), budget ${fmtEur(params.budgetCents)}, ${params.startDate} to ${params.endDate}, status: ${project.status}`,
        projectId: project.id,
        shortCode: project.shortCode,
      };
    },

    async delete_project(params: { projectId: string }, ctx: ToolContext) {
      deps.assertPermission(ctx, PermissionKey.MANAGE_PROJECTS);
      const project = await deps.resolveProjectIdentifier(ctx, params.projectId);
      if ("error" in project) {
        return project;
      }

      const caller = deps.createProjectCaller(deps.createScopedCallerContext(ctx));
      try {
        await caller.delete({ id: project.id });
      } catch (error) {
        const mapped = deps.toAssistantProjectNotFoundError(error, params.projectId);
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["project"],
        success: true,
        message: `Deleted project: ${project.name} (${project.shortCode})`,
      };
    },

    async generate_project_cover(
      params: { projectId: string; prompt?: string },
      ctx: ToolContext,
    ) {
      deps.assertPermission(ctx, PermissionKey.MANAGE_PROJECTS);
      const caller = deps.createProjectCaller(deps.createScopedCallerContext(ctx));
      const project = await deps.resolveProjectIdentifier(ctx, params.projectId);
      if ("error" in project) {
        return project;
      }

      const { coverImageUrl } = await caller.generateCover({
        projectId: project.id,
        ...(params.prompt !== undefined ? { prompt: params.prompt } : {}),
      });

      return {
        __action: "invalidate",
        scope: ["project"],
        success: true,
        message: `Generated cover art for project "${project.name}"`,
        coverImageUrl: coverImageUrl.slice(0, 100) + "...[truncated]",
      };
    },

    async remove_project_cover(params: { projectId: string }, ctx: ToolContext) {
      deps.assertPermission(ctx, PermissionKey.MANAGE_PROJECTS);
      const caller = deps.createProjectCaller(deps.createScopedCallerContext(ctx));
      const project = await deps.resolveProjectIdentifier(ctx, params.projectId);
      if ("error" in project) {
        return project;
      }

      await caller.removeCover({ projectId: project.id });

      return {
        __action: "invalidate",
        scope: ["project"],
        success: true,
        message: `Removed cover art from project "${project.name}"`,
      };
    },
  };
}
