import { z } from "zod";
import { TRPCError } from "@trpc/server";
import {
  getEntitlementBalance as getEntitlementBalanceUseCase,
  getEntitlementBalanceDetail as getEntitlementBalanceDetailUseCase,
  getEntitlementSync,
  getEntitlementYearSummary as getEntitlementYearSummaryUseCase,
  getEntitlementYearSummaryDetail as getEntitlementYearSummaryDetailUseCase,
  setEntitlement as setEntitlementUseCase,
  bulkSetEntitlements as bulkSetEntitlementsUseCase,
  type ReadEntitlementBalanceDeps,
} from "@nexus/application";
import { createAuditEntry } from "../lib/audit.js";
import { loadResourceHolidayContext } from "../lib/resource-holiday-context.js";
import {
  countCalendarDaysInPeriod,
  countVacationChargeableDays,
} from "../lib/vacation-day-count.js";
import {
  countVacationChargeableDaysFromSnapshot,
  parseVacationSnapshotDateList,
} from "../lib/vacation-deduction-snapshot.js";
import type { TRPCContext } from "../trpc.js";
import { buildVacationPreview } from "./vacation-read-support.js";

export const EntitlementBalanceInputSchema = z.object({
  resourceId: z.string(),
  year: z.number().int().min(2000).max(2100).default(new Date().getFullYear()),
});

export const EntitlementGetInputSchema = z.object({
  resourceId: z.string(),
  year: z.number().int(),
});

export const EntitlementSetInputSchema = z.object({
  resourceId: z.string(),
  year: z.number().int(),
  entitledDays: z.number().min(0).max(365),
});

export const EntitlementBulkSetInputSchema = z.object({
  year: z.number().int(),
  entitledDays: z.number().min(0).max(365),
  resourceIds: z.array(z.string()).optional(),
});

export const EntitlementYearSummaryInputSchema = z.object({
  year: z.number().int().min(2000).max(2100).default(new Date().getFullYear()),
  chapter: z.string().optional(),
});

export const EntitlementYearSummaryDetailInputSchema = EntitlementYearSummaryInputSchema.extend({
  resourceName: z.string().optional(),
});

function buildReadDeps(db: TRPCContext["db"]): ReadEntitlementBalanceDeps {
  return {
    loadResourceHolidayContext: (innerDb, resourceId, periodStart, periodEnd) =>
      loadResourceHolidayContext(innerDb as TRPCContext["db"], resourceId, periodStart, periodEnd),
    countCalendarDaysInPeriod,
    countVacationChargeableDays,
    countVacationChargeableDaysFromSnapshot,
    parseVacationSnapshotDateList,
    buildVacationPreview,
  };
}

type EntitlementReadContext = Pick<TRPCContext, "db" | "dbUser">;
type EntitlementWriteContext = Pick<TRPCContext, "db" | "dbUser">;

export async function getEntitlementBalance(
  ctx: EntitlementReadContext,
  input: z.infer<typeof EntitlementBalanceInputSchema>,
) {
  if (ctx.dbUser) {
    const allowedRoles = ["ADMIN", "MANAGER", "CONTROLLER"];
    if (!allowedRoles.includes(ctx.dbUser.systemRole)) {
      const resource = await ctx.db.resource.findUnique({
        where: { id: input.resourceId },
        select: { userId: true },
      });
      if (!resource || resource.userId !== ctx.dbUser.id) {
        throw new TRPCError({
          code: "FORBIDDEN",
          message: "You can only view your own vacation balance",
        });
      }
    }
  }

  return getEntitlementBalanceUseCase(ctx.db, input, buildReadDeps(ctx.db));
}

export async function getEntitlementBalanceDetail(
  ctx: EntitlementReadContext,
  input: z.infer<typeof EntitlementBalanceInputSchema>,
) {
  if (ctx.dbUser) {
    const allowedRoles = ["ADMIN", "MANAGER", "CONTROLLER"];
    if (!allowedRoles.includes(ctx.dbUser.systemRole)) {
      const resource = await ctx.db.resource.findUnique({
        where: { id: input.resourceId },
        select: { userId: true },
      });
      if (!resource || resource.userId !== ctx.dbUser.id) {
        throw new TRPCError({
          code: "FORBIDDEN",
          message: "You can only view your own vacation balance",
        });
      }
    }
  }

  return getEntitlementBalanceDetailUseCase(ctx.db, input, buildReadDeps(ctx.db));
}

export async function getEntitlement(
  ctx: Pick<TRPCContext, "db">,
  input: z.infer<typeof EntitlementGetInputSchema>,
) {
  return getEntitlementSync(ctx.db, input, buildReadDeps(ctx.db));
}

export async function setEntitlement(
  ctx: EntitlementWriteContext,
  input: z.infer<typeof EntitlementSetInputSchema>,
) {
  const { result, existing } = await setEntitlementUseCase(ctx.db, input);

  if (existing) {
    void createAuditEntry({
      db: ctx.db,
      entityType: "VacationEntitlement",
      entityId: result.id,
      entityName: `Entitlement ${input.resourceId} / ${input.year}`,
      action: "UPDATE",
      ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
      before: existing as unknown as Record<string, unknown>,
      after: result as unknown as Record<string, unknown>,
      source: "ui",
      summary: `Updated entitlement from ${existing.entitledDays} to ${input.entitledDays} days (${input.year})`,
    });
  } else {
    void createAuditEntry({
      db: ctx.db,
      entityType: "VacationEntitlement",
      entityId: result.id,
      entityName: `Entitlement ${input.resourceId} / ${input.year}`,
      action: "CREATE",
      ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
      after: result as unknown as Record<string, unknown>,
      source: "ui",
      summary: `Set entitlement to ${input.entitledDays} days (${input.year})`,
    });
  }

  return result;
}

export async function bulkSetEntitlements(
  ctx: EntitlementWriteContext,
  input: z.infer<typeof EntitlementBulkSetInputSchema>,
) {
  const result = await bulkSetEntitlementsUseCase(ctx.db, input);

  void createAuditEntry({
    db: ctx.db,
    entityType: "VacationEntitlement",
    entityId: `bulk-${input.year}`,
    entityName: `Bulk Entitlement ${input.year}`,
    action: "UPDATE",
    ...(ctx.dbUser?.id ? { userId: ctx.dbUser.id } : {}),
    after: {
      year: input.year,
      entitledDays: input.entitledDays,
      resourceCount: result.updated,
    } as unknown as Record<string, unknown>,
    source: "ui",
    summary: `Bulk set entitlement to ${input.entitledDays} days for ${result.updated} resources (${input.year})`,
  });

  return result;
}

export async function getEntitlementYearSummary(
  ctx: Pick<TRPCContext, "db">,
  input: z.infer<typeof EntitlementYearSummaryInputSchema>,
) {
  return getEntitlementYearSummaryUseCase(ctx.db, input, buildReadDeps(ctx.db));
}

export async function getEntitlementYearSummaryDetail(
  ctx: Pick<TRPCContext, "db">,
  input: z.infer<typeof EntitlementYearSummaryDetailInputSchema>,
) {
  return getEntitlementYearSummaryDetailUseCase(ctx.db, input, buildReadDeps(ctx.db));
}
