import { Prisma, VacationType, type PrismaClient } from "@capakraken/db";
import { DEFAULT_CALCULATION_RULES } from "@capakraken/engine";
import type { AbsenceDay, CalculationRule } from "@capakraken/shared";
import { logger } from "../lib/logger.js";

function isMissingOptionalTableError(error: unknown, tableHints: string[]): boolean {
  if (error instanceof Prisma.PrismaClientKnownRequestError) {
    if (error.code !== "P2021") {
      return false;
    }
    const table = typeof error.meta?.table === "string" ? error.meta.table.toLowerCase() : "";
    const message = error.message.toLowerCase();
    return tableHints.some((hint) => table.includes(hint) || message.includes(hint));
  }

  if (typeof error !== "object" || error === null || !("code" in error)) {
    return false;
  }

  const candidate = error as {
    code?: unknown;
    message?: unknown;
    meta?: { table?: unknown };
  };
  const code = typeof candidate.code === "string" ? candidate.code : "";
  if (code !== "P2021") {
    return false;
  }
  const table = typeof candidate.meta?.table === "string" ? candidate.meta.table.toLowerCase() : "";
  const message = typeof candidate.message === "string" ? candidate.message.toLowerCase() : "";
  return tableHints.some((hint) => table.includes(hint) || message.includes(hint));
}

export async function loadTimelineCalculationRules(
  db: PrismaClient,
): Promise<CalculationRule[]> {
  const calculationRuleModel = (db as PrismaClient & {
    calculationRule?: { findMany?: (args: unknown) => Promise<unknown[]> };
  }).calculationRule;

  if (!calculationRuleModel || typeof calculationRuleModel.findMany !== "function") {
    return DEFAULT_CALCULATION_RULES;
  }

  try {
    const rules = await calculationRuleModel.findMany({
      where: { isActive: true },
      orderBy: [{ priority: "desc" }],
    });
    if (rules.length > 0) {
      return rules as unknown as CalculationRule[];
    }
  } catch (error) {
    if (!isMissingOptionalTableError(error, ["calculationrule", "calculation_rule", "calculation_rules"])) {
      logger.error({ err: error }, "Failed to load active calculation rules for timeline");
      throw error;
    }
  }
  return DEFAULT_CALCULATION_RULES;
}

export async function buildTimelineAbsenceDays(
  db: PrismaClient,
  resourceId: string,
  startDate: Date,
  endDate: Date,
): Promise<{ absenceDays: AbsenceDay[]; legacyVacationDates: Date[] }> {
  const absenceDays: AbsenceDay[] = [];
  const legacyVacationDates: Date[] = [];

  try {
    const vacations = await db.vacation.findMany({
      where: {
        resourceId,
        status: "APPROVED",
        startDate: { lte: endDate },
        endDate: { gte: startDate },
      },
      select: { startDate: true, endDate: true, type: true, isHalfDay: true },
    });

    for (const vacation of vacations) {
      const cur = new Date(vacation.startDate);
      cur.setUTCHours(0, 0, 0, 0);
      const vacationEnd = new Date(vacation.endDate);
      vacationEnd.setUTCHours(0, 0, 0, 0);

      const triggerType = vacation.type === VacationType.SICK
        ? "SICK" as const
        : vacation.type === VacationType.PUBLIC_HOLIDAY
          ? "PUBLIC_HOLIDAY" as const
          : "VACATION" as const;

      while (cur <= vacationEnd) {
        absenceDays.push({
          date: new Date(cur),
          type: triggerType,
          ...(vacation.isHalfDay ? { isHalfDay: true } : {}),
        });
        if (triggerType === "VACATION") {
          legacyVacationDates.push(new Date(cur));
        }
        cur.setUTCDate(cur.getUTCDate() + 1);
      }
    }
  } catch (error) {
    if (!isMissingOptionalTableError(error, ["vacation", "vacations"])) {
      logger.error(
        { err: error, resourceId, startDate, endDate },
        "Failed to load timeline absence days",
      );
      throw error;
    }
  }

  return { absenceDays, legacyVacationDates };
}
