import { VacationType } from "@capakraken/db";
import { 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 ResolvedResource = {
  id: string;
  displayName: string;
};

type VacationRecord = {
  id: string;
  status: string;
  effectiveDays?: number | null;
  warnings?: unknown;
  resource?: {
    displayName?: string | null;
  } | null;
};

type PendingVacationApproval = {
  id: string;
  resource: {
    displayName: string;
    eid: string | null;
    chapter: string | null;
  };
  type: string;
  startDate: Date;
  endDate: Date;
  isHalfDay: boolean;
};

type VacationEntitlementDeps = {
  createVacationCaller: (ctx: TRPCContext) => {
    create: (params: {
      resourceId: string;
      type: VacationType;
      startDate: Date;
      endDate: Date;
      isHalfDay?: boolean;
      halfDayPart?: "MORNING" | "AFTERNOON";
      note?: string;
    }) => Promise<VacationRecord>;
    getById: (params: { id: string }) => Promise<VacationRecord>;
    approve: (params: { id: string }) => Promise<VacationRecord>;
    reject: (params: { id: string; rejectionReason?: string }) => Promise<VacationRecord>;
    cancel: (params: { id: string }) => Promise<VacationRecord>;
    getPendingApprovals: () => Promise<PendingVacationApproval[]>;
    getTeamOverlapDetail: (params: {
      resourceId: string;
      startDate: Date;
      endDate: Date;
    }) => Promise<unknown>;
  };
  createEntitlementCaller: (ctx: TRPCContext) => {
    getYearSummaryDetail: (params: {
      year: number;
      resourceName?: string;
    }) => Promise<unknown>;
    set: (params: {
      resourceId: string;
      year: number;
      entitledDays: number;
    }) => Promise<unknown>;
  };
  createScopedCallerContext: (ctx: ToolContext) => TRPCContext;
  resolveResourceIdentifier: (
    ctx: ToolContext,
    identifier: string,
  ) => Promise<ResolvedResource | AssistantToolErrorResult>;
  parseIsoDate: (value: string, fieldName: string) => Date;
  fmtDate: (value: Date | null | undefined) => string | null;
  parseAssistantVacationRequestType: (input: string) => VacationType;
  toAssistantVacationCreationError: (
    error: unknown,
  ) => AssistantToolErrorResult | null;
  toAssistantVacationMutationError: (
    error: unknown,
    action: "approve" | "reject" | "cancel",
  ) => AssistantToolErrorResult | null;
  toAssistantEntitlementMutationError: (
    error: unknown,
  ) => AssistantToolErrorResult | null;
};

export const vacationEntitlementToolDefinitions: ToolDef[] = withToolAccess([
  {
    type: "function",
    function: {
      name: "create_vacation",
      description: "Create a vacation/leave request through the real vacation workflow. Any authenticated user can request leave for their own resource; manager/admin can create requests for others. Always confirm with the user.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, EID, or display name" },
          type: {
            type: "string",
            enum: ["ANNUAL", "SICK", "OTHER"],
            description: "Vacation type. PUBLIC_HOLIDAY requests are managed through holiday calendars, not manual vacation requests.",
          },
          startDate: { type: "string", description: "Start date YYYY-MM-DD" },
          endDate: { type: "string", description: "End date YYYY-MM-DD" },
          isHalfDay: { type: "boolean", description: "Half day? Default: false" },
          halfDayPart: { type: "string", description: "MORNING or AFTERNOON (if half day)" },
          note: { type: "string", description: "Optional note" },
        },
        required: ["resourceId", "type", "startDate", "endDate"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "approve_vacation",
      description: "Approve a vacation request through the real vacation workflow. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          vacationId: { type: "string", description: "Vacation ID" },
        },
        required: ["vacationId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "reject_vacation",
      description: "Reject a pending vacation request through the real vacation workflow. Manager or admin role required. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          vacationId: { type: "string", description: "Vacation ID" },
          reason: { type: "string", description: "Rejection reason" },
        },
        required: ["vacationId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "cancel_vacation",
      description: "Cancel a vacation request through the real vacation workflow. Users can cancel their own requests; manager/admin can cancel any request. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          vacationId: { type: "string", description: "Vacation ID" },
        },
        required: ["vacationId"],
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_pending_vacation_approvals",
      description: "List vacation requests awaiting approval.",
      parameters: {
        type: "object",
        properties: {
          limit: { type: "integer", description: "Max results. Default: 20" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "get_team_vacation_overlap",
      description: "Check if team members have overlapping vacations in a date range.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID to check overlap for" },
          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_entitlement_summary",
      description: "Get vacation entitlement year summary for all resources or a specific resource.",
      parameters: {
        type: "object",
        properties: {
          year: { type: "integer", description: "Year. Default: current year" },
          resourceName: { type: "string", description: "Filter by resource name (optional)" },
        },
      },
    },
  },
  {
    type: "function",
    function: {
      name: "set_entitlement",
      description: "Set the annual vacation entitlement for a resource/year through the real entitlement workflow. Manager or admin role required. Carryover is computed automatically. Always confirm first.",
      parameters: {
        type: "object",
        properties: {
          resourceId: { type: "string", description: "Resource ID, EID, or display name" },
          year: { type: "integer", description: "Year" },
          entitledDays: { type: "number", description: "Number of entitled vacation days" },
        },
        required: ["resourceId", "year", "entitledDays"],
      },
    },
  },
], {
  approve_vacation: {
    allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER],
  },
  reject_vacation: {
    allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER],
  },
  get_pending_vacation_approvals: {
    allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER],
  },
  get_entitlement_summary: {
    allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER],
  },
  set_entitlement: {
    allowedSystemRoles: [SystemRole.ADMIN, SystemRole.MANAGER],
  },
});

export function createVacationEntitlementExecutors(
  deps: VacationEntitlementDeps,
): Record<string, ToolExecutor> {
  return {
    async create_vacation(
      params: {
        resourceId: string;
        type: string;
        startDate: string;
        endDate: string;
        isHalfDay?: boolean;
        halfDayPart?: string;
        note?: string;
      },
      ctx: ToolContext,
    ) {
      const resource = await deps.resolveResourceIdentifier(ctx, params.resourceId);
      if ("error" in resource) {
        return resource;
      }

      const caller = deps.createVacationCaller(deps.createScopedCallerContext(ctx));
      const type = deps.parseAssistantVacationRequestType(params.type);
      let vacation;
      try {
        vacation = await caller.create({
          resourceId: resource.id,
          type,
          startDate: deps.parseIsoDate(params.startDate, "startDate"),
          endDate: deps.parseIsoDate(params.endDate, "endDate"),
          ...(params.isHalfDay !== undefined ? { isHalfDay: params.isHalfDay } : {}),
          ...(params.halfDayPart !== undefined ? { halfDayPart: params.halfDayPart as "MORNING" | "AFTERNOON" } : {}),
          ...(params.note !== undefined ? { note: params.note } : {}),
        });
      } catch (error) {
        const mapped = deps.toAssistantVacationCreationError(error);
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      const effectiveDays = typeof vacation.effectiveDays === "number"
        ? vacation.effectiveDays
        : null;

      return {
        __action: "invalidate",
        scope: ["vacation"],
        success: true,
        message: `Created ${type} for ${resource.displayName}: ${params.startDate} to ${params.endDate} (status: ${vacation.status}${effectiveDays !== null ? `, deducted ${effectiveDays} day(s)` : ""})`,
        vacationId: vacation.id,
        vacation,
      };
    },

    async approve_vacation(
      params: { vacationId: string },
      ctx: ToolContext,
    ) {
      const caller = deps.createVacationCaller(deps.createScopedCallerContext(ctx));
      let existing;
      try {
        existing = await caller.getById({ id: params.vacationId });
      } catch (error) {
        const mapped = deps.toAssistantVacationMutationError(error, "approve");
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      let approved;
      try {
        approved = await caller.approve({ id: params.vacationId });
      } catch (error) {
        const mapped = deps.toAssistantVacationMutationError(error, "approve");
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["vacation"],
        success: true,
        warnings: approved.warnings,
        vacation: approved,
        message: `Approved vacation for ${existing.resource?.displayName ?? params.vacationId}`,
      };
    },

    async reject_vacation(
      params: { vacationId: string; reason?: string },
      ctx: ToolContext,
    ) {
      const caller = deps.createVacationCaller(deps.createScopedCallerContext(ctx));
      let existing;
      try {
        existing = await caller.getById({ id: params.vacationId });
      } catch (error) {
        const mapped = deps.toAssistantVacationMutationError(error, "reject");
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      let rejected;
      try {
        rejected = await caller.reject({
          id: params.vacationId,
          ...(params.reason !== undefined ? { rejectionReason: params.reason } : {}),
        });
      } catch (error) {
        const mapped = deps.toAssistantVacationMutationError(error, "reject");
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["vacation"],
        success: true,
        vacation: rejected,
        message: `Rejected vacation for ${existing.resource?.displayName ?? params.vacationId}${params.reason ? `: ${params.reason}` : ""}`,
      };
    },

    async cancel_vacation(
      params: { vacationId: string },
      ctx: ToolContext,
    ) {
      const caller = deps.createVacationCaller(deps.createScopedCallerContext(ctx));
      let existing;
      try {
        existing = await caller.getById({ id: params.vacationId });
      } catch (error) {
        const mapped = deps.toAssistantVacationMutationError(error, "cancel");
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      let cancelled;
      try {
        cancelled = await caller.cancel({ id: params.vacationId });
      } catch (error) {
        const mapped = deps.toAssistantVacationMutationError(error, "cancel");
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["vacation"],
        success: true,
        vacation: cancelled,
        message: `Cancelled vacation for ${existing.resource?.displayName ?? params.vacationId}`,
      };
    },

    async get_pending_vacation_approvals(
      params: { limit?: number },
      ctx: ToolContext,
    ) {
      const limit = Math.min(params.limit ?? 20, 50);
      const caller = deps.createVacationCaller(deps.createScopedCallerContext(ctx));
      const vacations = await caller.getPendingApprovals();

      return vacations.map((vacation) => ({
        id: vacation.id,
        resource: vacation.resource.displayName,
        eid: vacation.resource.eid,
        chapter: vacation.resource.chapter,
        type: vacation.type,
        start: deps.fmtDate(vacation.startDate),
        end: deps.fmtDate(vacation.endDate),
        isHalfDay: vacation.isHalfDay,
      })).slice(0, limit);
    },

    async get_team_vacation_overlap(
      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.createVacationCaller(deps.createScopedCallerContext(ctx));
      return caller.getTeamOverlapDetail({
        resourceId: resource.id,
        startDate: deps.parseIsoDate(params.startDate, "startDate"),
        endDate: deps.parseIsoDate(params.endDate, "endDate"),
      });
    },

    async get_entitlement_summary(
      params: { year?: number; resourceName?: string },
      ctx: ToolContext,
    ) {
      const year = params.year ?? new Date().getFullYear();
      const caller = deps.createEntitlementCaller(deps.createScopedCallerContext(ctx));
      return caller.getYearSummaryDetail({
        year,
        ...(params.resourceName ? { resourceName: params.resourceName } : {}),
      });
    },

    async set_entitlement(
      params: {
        resourceId: string;
        year: number;
        entitledDays: number;
        carryoverDays?: number;
      },
      ctx: ToolContext,
    ) {
      if (params.carryoverDays !== undefined) {
        return {
          error: "Manual carryoverDays is not supported here. Carryover is computed automatically from prior-year balances.",
        };
      }

      const resource = await deps.resolveResourceIdentifier(ctx, params.resourceId);
      if ("error" in resource) {
        return resource;
      }

      const caller = deps.createEntitlementCaller(deps.createScopedCallerContext(ctx));
      let entitlement;
      try {
        entitlement = await caller.set({
          resourceId: resource.id,
          year: params.year,
          entitledDays: params.entitledDays,
        });
      } catch (error) {
        const mapped = deps.toAssistantEntitlementMutationError(error);
        if (mapped) {
          return mapped;
        }
        throw error;
      }

      return {
        __action: "invalidate",
        scope: ["vacation"],
        success: true,
        entitlement,
        message: `Set entitlement for ${resource.displayName} (${params.year}): ${params.entitledDays} days`,
      };
    },
  };
}
