import type { Prisma } from "@capakraken/db";
import { DispoImportSourceKind, StagedRecordStatus } from "@capakraken/db";
import { parseDispoPlanningWorkbook } from "./parse-dispo-matrix.js";
import {
  DISPO_PLANNING_SHEET,
  ensureImportBatch,
  finalizeImportBatchStage,
  getWorkbookFileName,
  isPseudoDemandResourceIdentity,
  type DispoImportDbClient,
  type DispoPlanningImportInput,
} from "./shared.js";

export interface StageDispoPlanningResult {
  batchId: string;
  counts: {
    stagedAssignments: number;
    stagedAvailabilityRules: number;
    stagedVacations: number;
    unresolved: number;
    warnings: number;
  };
}

export async function stageDispoPlanningData(
  db: DispoImportDbClient,
  input: DispoPlanningImportInput,
): Promise<StageDispoPlanningResult> {
  const batchInput: {
    importBatchId?: string;
    notes?: string | null;
    planningSourceFile: string;
  } = {
    planningSourceFile: getWorkbookFileName(input.planningWorkbookPath),
  };

  if (input.importBatchId !== undefined) {
    batchInput.importBatchId = input.importBatchId;
  }
  if (input.notes !== undefined) {
    batchInput.notes = input.notes;
  }

  const batch = await ensureImportBatch(db, batchInput);
  const parsed = await parseDispoPlanningWorkbook(input.planningWorkbookPath);
  const excludedIds = new Set(input.excludedResourceExternalIds ?? []);
  const filteredAssignments = parsed.assignments.filter(
    (assignment) =>
      !excludedIds.has(assignment.resourceExternalId) &&
      !isPseudoDemandResourceIdentity(assignment.resourceExternalId),
  );
  const filteredVacations = parsed.vacations.filter(
    (vacation) =>
      !excludedIds.has(vacation.resourceExternalId) &&
      !isPseudoDemandResourceIdentity(vacation.resourceExternalId),
  );
  const filteredAvailabilityRules = parsed.availabilityRules.filter(
    (rule) =>
      !excludedIds.has(rule.resourceExternalId) &&
      !isPseudoDemandResourceIdentity(rule.resourceExternalId),
  );
  const filteredUnresolved = parsed.unresolved.filter(
    (record) =>
      !record.resourceExternalId ||
      (!excludedIds.has(record.resourceExternalId) &&
        !isPseudoDemandResourceIdentity(record.resourceExternalId)),
  );
  const sourceWorkbook = getWorkbookFileName(input.planningWorkbookPath);

  await db.stagedAssignment.deleteMany({
    where: {
      importBatchId: batch.id,
      sourceKind: DispoImportSourceKind.PLANNING,
    },
  });
  await db.stagedVacation.deleteMany({
    where: {
      importBatchId: batch.id,
      sourceKind: DispoImportSourceKind.PLANNING,
    },
  });
  await db.stagedAvailabilityRule.deleteMany({
    where: {
      importBatchId: batch.id,
      sourceKind: DispoImportSourceKind.PLANNING,
    },
  });
  await db.stagedUnresolvedRecord.deleteMany({
    where: {
      importBatchId: batch.id,
      sourceKind: DispoImportSourceKind.PLANNING,
    },
  });

  if (filteredAssignments.length > 0) {
    await db.stagedAssignment.createMany({
      data: filteredAssignments.map((assignment) => ({
        importBatchId: batch.id,
        status: assignment.warnings.length > 0
          ? StagedRecordStatus.PARSED
          : StagedRecordStatus.NORMALIZED,
        sourceKind: DispoImportSourceKind.PLANNING,
        sourceWorkbook,
        sourceSheet: DISPO_PLANNING_SHEET,
        sourceRow: assignment.sourceRow,
        sourceColumn: assignment.sourceColumn,
        resourceExternalId: assignment.resourceExternalId,
        projectKey: assignment.projectKey,
        assignmentDate: assignment.assignmentDate,
        startDate: assignment.assignmentDate,
        endDate: assignment.assignmentDate,
        hoursPerDay: assignment.hoursPerDay,
        percentage: assignment.percentage,
        slotFraction: assignment.slotFraction,
        roleToken: assignment.roleToken,
        roleName: assignment.roleName,
        chapterToken: assignment.chapterToken,
        utilizationCategoryCode: assignment.utilizationCategoryCode,
        winProbability: assignment.winProbability,
        isInternal: assignment.isInternal,
        isUnassigned: assignment.isUnassigned,
        isTbd: assignment.isTbd,
        warnings: assignment.warnings,
        rawPayload: {
          rawToken: assignment.rawToken,
        } as Prisma.InputJsonValue,
        normalizedData: {
          assignmentDate: assignment.assignmentDate.toISOString().slice(0, 10),
          chapterToken: assignment.chapterToken,
          hoursPerDay: assignment.hoursPerDay,
          percentage: assignment.percentage,
          roleToken: assignment.roleToken,
          utilizationCategoryCode: assignment.utilizationCategoryCode,
          winProbability: assignment.winProbability,
        } as Prisma.InputJsonValue,
      })),
    });
  }

  if (filteredVacations.length > 0) {
    await db.stagedVacation.createMany({
      data: filteredVacations.map((vacation) => ({
        importBatchId: batch.id,
        status: vacation.warnings.length > 0
          ? StagedRecordStatus.PARSED
          : StagedRecordStatus.NORMALIZED,
        sourceKind: DispoImportSourceKind.PLANNING,
        sourceWorkbook,
        sourceSheet: DISPO_PLANNING_SHEET,
        sourceRow: vacation.sourceRow,
        sourceColumn: vacation.sourceColumn,
        resourceExternalId: vacation.resourceExternalId,
        vacationType: vacation.vacationType,
        startDate: vacation.startDate,
        endDate: vacation.endDate,
        note: vacation.note,
        holidayName: vacation.holidayName,
        isHalfDay: vacation.isHalfDay,
        halfDayPart: vacation.halfDayPart,
        isPublicHoliday: vacation.isPublicHoliday,
        warnings: vacation.warnings,
        rawPayload: {
          rawToken: vacation.rawToken,
        } as Prisma.InputJsonValue,
        normalizedData: {
          holidayName: vacation.holidayName,
          isHalfDay: vacation.isHalfDay,
          isPublicHoliday: vacation.isPublicHoliday,
          note: vacation.note,
        } as Prisma.InputJsonValue,
      })),
    });
  }

  if (filteredAvailabilityRules.length > 0) {
    await db.stagedAvailabilityRule.createMany({
      data: filteredAvailabilityRules.map((rule) => ({
        importBatchId: batch.id,
        status: rule.warnings.length > 0
          ? StagedRecordStatus.PARSED
          : StagedRecordStatus.NORMALIZED,
        sourceKind: DispoImportSourceKind.PLANNING,
        sourceWorkbook,
        sourceSheet: DISPO_PLANNING_SHEET,
        sourceRow: rule.sourceRow,
        sourceColumn: rule.sourceColumn,
        resourceExternalId: rule.resourceExternalId,
        ruleType: rule.ruleType,
        weekday: null,
        effectiveStartDate: rule.effectiveStartDate,
        effectiveEndDate: rule.effectiveEndDate,
        availableHours: rule.availableHours,
        percentage: rule.percentage,
        isResolved: rule.isResolved,
        warnings: rule.warnings,
        rawPayload: {
          rawToken: rule.rawToken,
        } as Prisma.InputJsonValue,
        normalizedData: {
          availableHours: rule.availableHours,
          percentage: rule.percentage,
          ruleType: rule.ruleType,
        } as Prisma.InputJsonValue,
      })),
    });
  }

  if (filteredUnresolved.length > 0) {
    await db.stagedUnresolvedRecord.createMany({
      data: filteredUnresolved.map((record) => ({
        importBatchId: batch.id,
        status: StagedRecordStatus.UNRESOLVED,
        sourceKind: DispoImportSourceKind.PLANNING,
        sourceWorkbook,
        sourceSheet: DISPO_PLANNING_SHEET,
        sourceRow: record.sourceRow,
        sourceColumn: record.sourceColumn ?? null,
        recordType: record.recordType,
        resourceExternalId: record.resourceExternalId ?? null,
        projectKey: record.projectKey ?? null,
        message: record.message,
        resolutionHint: record.resolutionHint ?? null,
        warnings: record.warnings,
        rawPayload: {} as Prisma.InputJsonValue,
        normalizedData: record.normalizedData as Prisma.InputJsonValue,
      })),
    });
  }

  const warningCount =
    parsed.warnings.length +
    filteredAssignments.reduce((count, assignment) => count + assignment.warnings.length, 0) +
    filteredVacations.reduce((count, vacation) => count + vacation.warnings.length, 0) +
    filteredAvailabilityRules.reduce((count, rule) => count + rule.warnings.length, 0);
  const summary = {
    stagedAssignments: filteredAssignments.length,
    stagedAvailabilityRules: filteredAvailabilityRules.length,
    stagedVacations: filteredVacations.length,
    unresolved: filteredUnresolved.length,
    warnings: warningCount,
  };

  await finalizeImportBatchStage(db, {
    batchId: batch.id,
    existingSummary: batch.summary,
    key: "planning",
    summary,
  });

  return {
    batchId: batch.id,
    counts: summary,
  };
}
