import { AllocationStatus, PermissionKey, SystemRole } from "@capakraken/shared";
import type { TRPCContext } from "../../trpc.js";
import { withToolAccess, type ToolContext, type ToolDef, type ToolExecutor } from "./shared.js";

type AssistantToolErrorResult = { error: string };

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

type ResolvedResource = {
  id: string;
  displayName: string;
};

type ResolvedRole = {
  id: string;
  name: string;
};

type DemandRecord = {
  id: string;
  status: string;
  headcount: number;
  hoursPerDay: number;
  startDate: Date | null;
  endDate: Date | null;
  role?: string | null;
  roleEntity?: { name?: string | null } | null;
  project: {
    name: string;
    shortCode: string;
  };
  assignments: unknown[];
};

type DemandAssignmentResult = {
  assignment: {
    id: string;
  };
  demandRequirement: {
    role?: string | null;
    roleEntity?: { name?: string | null } | null;
    project: {
      name: string;
      shortCode: string;
    };
  };
};

type StaffingDemandDeps = {
  assertPermission: (ctx: ToolContext, perm: PermissionKey) => void;
  createAllocationCaller: (ctx: TRPCContext) => {
    listDemands: (params: {
      projectId?: string;
      status?: AllocationStatus;
    }) => Promise<DemandRecord[]>;
    createDemand: (params: {
      projectId: string;
      roleId: string;
      role: string;
      headcount: number;
      hoursPerDay: number;
      startDate: Date;
      endDate: Date;
    }) => Promise<{ id: string }>;
    assignResourceToDemand: (params: {
      demandRequirementId: string;
      resourceId: string;
    }) => Promise<DemandAssignmentResult>;
    getResourceAvailabilitySummary: (params: {
      resourceId: string;
      startDate: Date;
      endDate: Date;
    }) => Promise<unknown>;
  };
  createStaffingCaller: (ctx: TRPCContext) => {
    getProjectStaffingSuggestions: (params: {
      projectId: string;
      roleName?: string;
      startDate?: Date;
      endDate?: Date;
      limit?: number;
    }) => Promise<unknown>;
    searchCapacity: (params: {
      startDate: Date;
      endDate: Date;
      minHoursPerDay: number;
      roleName?: string;
      chapter?: string;
      limit?: number;
    }) => Promise<unknown>;
  };
  createRoleCaller: (ctx: TRPCContext) => {
    resolveByIdentifier: (params: { identifier: string }) => Promise<ResolvedRole>;
  };
  createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
  resolveProjectIdentifier: (
    ctx: ToolContext,
    identifier: string,
  ) => Promise<ResolvedProject | AssistantToolErrorResult>;
  resolveResourceIdentifier: (
    ctx: ToolContext,
    identifier: string,
  ) => Promise<ResolvedResource | AssistantToolErrorResult>;
  resolveEntityOrAssistantError: <T>(
    resolve: () => Promise<T>,
    notFoundMessage: string,
  ) => Promise<T | AssistantToolErrorResult>;
  parseIsoDate: (value: string, fieldName: string) => Date;
  parseOptionalIsoDate: (value: string | undefined, fieldName: string) => Date | undefined;
  fmtDate: (value: Date | null | undefined) => string | null;
  toAssistantDemandCreationError: (
    error: unknown,
  ) => AssistantToolErrorResult | null;
  toAssistantDemandFillError: (
    error: unknown,
  ) => AssistantToolErrorResult | null;
};

export const staffingDemandReadToolDefinitions: ToolDef[] = withToolAccess([
  {
    type: "function",
    function: {
      name: "list_demands",
      description: "List staffing demand requirements for projects. Shows open positions that need to be filled.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Filter by project ID or short code" },
          status: { type: "string", description: "Filter by status: OPEN, PARTIALLY_FILLED, FILLED, CANCELLED" },
          limit: { type: "integer", description: "Max results. Default: 30" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "check_resource_availability",
      description: "Check if a resource is available in a given date range (no conflicting allocations or vacations).",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, eid, or name" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
        },
        required: ["resourceId", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_staffing_suggestions",
      description: "Get AI-powered staffing suggestions for a project based on required skills, availability, and cost.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
          roleName: { type: "string", description: "Role to find candidates for" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
          limit: { type: "integer", description: "Max suggestions. Default: 5" },
        },
        required: ["projectId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "find_capacity",
      description: "Find resources with available capacity in a date range.",
      parameters: {
        type: "object",
        properties: {
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
          minHoursPerDay: { type: "number", description: "Minimum available hours/day. Default: 4" },
          roleName: { type: "string", description: "Filter by role name" },
          chapter: { type: "string", description: "Filter by chapter" },
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
        required: ["startDate", "endDate"],
      },
    },
  },
], {
  list_demands: {
    requiresPlanningRead: true,
  },
  check_resource_availability: {
    requiresPlanningRead: true,
  },
  get_staffing_suggestions: {
    requiresPlanningRead: true,
    requiresCostView: true,
  },
  find_capacity: {
    requiresPlanningRead: true,
  },
});

export const staffingDemandMutationToolDefinitions: ToolDef[] = withToolAccess([
  {
    type: "function",
    function: {
      name: "create_demand",
      description: "Create a staffing demand requirement on a project. Requires manageAllocations permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          projectId: { type: "string", description: "Project ID or short code" },
          roleName: { type: "string", description: "Role name for the demand" },
          headcount: { type: "integer", description: "Number of people needed. Default: 1" },
          hoursPerDay: { type: "number", description: "Hours per day required" },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
        },
        required: ["projectId", "roleName", "hoursPerDay", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "fill_demand",
      description: "Fill/assign a resource to an open demand requirement. Requires manageAllocations permission. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          demandId: { type: "string", description: "Demand requirement ID" },
          resourceId: { type: "string", description: "Resource ID or name to assign" },
        },
        required: ["demandId", "resourceId"],
      },
    },
  },
], {
  create_demand: {
    requiredPermissions: [PermissionKey.MANAGE_ALLOCATIONS],
    allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER],
  },
  fill_demand: {
    requiredPermissions: [PermissionKey.MANAGE_ALLOCATIONS],
    allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER],
  },
});

export function createStaffingDemandExecutors(
  deps: StaffingDemandDeps,
): Record<string, ToolExecutor> {
  return {
    async list_demands(
      params: { projectId?: string; status?: string; limit?: number },
      ctx: ToolContext,
    ) {
      const limit = Math.min(params.limit ?? 30, 50);
      const caller = deps.createAllocationCaller(deps.createScopedCallerContext(ctx));
      const resolvedProject = params.projectId
        ? await deps.resolveProjectIdentifier(ctx, params.projectId)
        : null;
      if (resolvedProject && "error" in resolvedProject) {
        return resolvedProject;
      }

      const demands = await caller.listDemands({
        ...(resolvedProject ? { projectId: resolvedProject.id } : {}),
        ...(params.status ? { status: params.status as AllocationStatus } : {}),
      });

      return demands.map((demand) => ({
        id: demand.id,
        project: demand.project.name,
        projectCode: demand.project.shortCode,
        role: demand.roleEntity?.name ?? demand.role ?? "Unspecified",
        status: demand.status,
        headcount: demand.headcount,
        filled: demand.assignments.length,
        remaining: demand.headcount - demand.assignments.length,
        hoursPerDay: demand.hoursPerDay,
        start: deps.fmtDate(demand.startDate),
        end: deps.fmtDate(demand.endDate),
      })).slice(0, limit);
    },

    async create_demand(
      params: {
        projectId: string;
        roleName: string;
        headcount?: number;
        hoursPerDay: number;
        startDate: string;
        endDate: string;
      },
      ctx: ToolContext,
    ) {
      deps.assertPermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const scopedContext = deps.createScopedCallerContext(ctx);
      const roleCaller = deps.createRoleCaller(scopedContext);
      const [project, role] = await Promise.all([
        deps.resolveProjectIdentifier(ctx, params.projectId),
        deps.resolveEntityOrAssistantError(
          () => roleCaller.resolveByIdentifier({ identifier: params.roleName }),
          `Role not found: ${params.roleName}`,
        ),
      ]);

      if ("error" in project) {
        return project;
      }
      if ("error" in role) {
        return role;
      }

      const caller = deps.createAllocationCaller(scopedContext);
      let demand;
      try {
        demand = await caller.createDemand({
          projectId: project.id,
          roleId: role.id,
          role: role.name,
          headcount: params.headcount ?? 1,
          hoursPerDay: params.hoursPerDay,
          startDate: deps.parseIsoDate(params.startDate, "startDate"),
          endDate: deps.parseIsoDate(params.endDate, "endDate"),
        });
      } catch (error) {
        const mapped = deps.toAssistantDemandCreationError(error);
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["allocation"],
        success: true,
        message: `Created demand: ${role.name} × ${params.headcount ?? 1} for ${project.name} (${project.shortCode}), ${params.hoursPerDay}h/day, ${params.startDate} to ${params.endDate}`,
        demandId: demand.id,
      };
    },

    async fill_demand(
      params: { demandId: string; resourceId: string },
      ctx: ToolContext,
    ) {
      deps.assertPermission(ctx, PermissionKey.MANAGE_ALLOCATIONS);
      const allocationCaller = deps.createAllocationCaller(deps.createScopedCallerContext(ctx));
      const resource = await deps.resolveResourceIdentifier(ctx, params.resourceId);
      if ("error" in resource) {
        return resource;
      }

      let result;
      try {
        result = await allocationCaller.assignResourceToDemand({
          demandRequirementId: params.demandId,
          resourceId: resource.id,
        });
      } catch (error) {
        const mapped = deps.toAssistantDemandFillError(error);
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      const roleName = result.demandRequirement.roleEntity?.name
        ?? result.demandRequirement.role
        ?? null;

      return {
        __action: "invalidate",
        scope: ["allocation", "timeline"],
        success: true,
        message: `Assigned ${resource.displayName} to ${roleName ?? "demand"} on ${result.demandRequirement.project.name} (${result.demandRequirement.project.shortCode})`,
        assignmentId: result.assignment.id,
      };
    },

    async check_resource_availability(
      params: { resourceId: string; startDate: string; endDate: string },
      ctx: ToolContext,
    ) {
      const resource = await deps.resolveResourceIdentifier(ctx, params.resourceId);
      if ("error" in resource) {
        return resource;
      }

      const caller = deps.createAllocationCaller(deps.createScopedCallerContext(ctx));
      return caller.getResourceAvailabilitySummary({
        resourceId: resource.id,
        startDate: deps.parseIsoDate(params.startDate, "startDate"),
        endDate: deps.parseIsoDate(params.endDate, "endDate"),
      });
    },

    async get_staffing_suggestions(
      params: {
        projectId: string;
        roleName?: string;
        startDate?: string;
        endDate?: string;
        limit?: number;
      },
      ctx: ToolContext,
    ) {
      const project = await deps.resolveProjectIdentifier(ctx, params.projectId);
      if ("error" in project) {
        return project;
      }

      const caller = deps.createStaffingCaller(deps.createScopedCallerContext(ctx));
      const startDate = deps.parseOptionalIsoDate(params.startDate, "startDate");
      const endDate = deps.parseOptionalIsoDate(params.endDate, "endDate");
      return caller.getProjectStaffingSuggestions({
        projectId: project.id,
        ...(params.roleName ? { roleName: params.roleName } : {}),
        ...(startDate ? { startDate } : {}),
        ...(endDate ? { endDate } : {}),
        ...(params.limit ? { limit: params.limit } : {}),
      });
    },

    async find_capacity(
      params: {
        startDate: string;
        endDate: string;
        minHoursPerDay?: number;
        roleName?: string;
        chapter?: string;
        limit?: number;
      },
      ctx: ToolContext,
    ) {
      const caller = deps.createStaffingCaller(deps.createScopedCallerContext(ctx));
      return caller.searchCapacity({
        startDate: deps.parseIsoDate(params.startDate, "startDate"),
        endDate: deps.parseIsoDate(params.endDate, "endDate"),
        minHoursPerDay: params.minHoursPerDay ?? 4,
        ...(params.roleName ? { roleName: params.roleName } : {}),
        ...(params.chapter ? { chapter: params.chapter } : {}),
        ...(params.limit ? { limit: params.limit } : {}),
      });
    },
  };
}
