import { DispoStagedRecordType, ResourceType } from "@capakraken/db";
import { createWeekdayAvailabilityFromFte } from "@capakraken/shared";
import {
  parseResourceRosterMasterWorkbook,
  type ParsedResourceRosterLevelAverage,
  type ParsedResourceRosterMasterWorkbook,
  type ParsedResourceRosterRate,
} from "./parse-resource-roster-master-workbook.js";
import {
  DISPO_ROSTER_SAP_SHEET,
  DISPO_ROSTER_SHEET,
  type ParsedRosterResource,
  type ParsedRosterWorkbook,
  type ParsedUnresolvedRecord,
  buildFallbackAccentureEmail,
  deriveCountryCodeFromMetroCity,
  deriveDisplayNameFromEnterpriseId,
  deriveNormalizedChapter,
  deriveRoleTokens,
  isPseudoDemandResourceIdentity,
  mapChargeabilityResourceType,
  normalizeNullableWorkbookValue,
  normalizeText,
  resolveCanonicalEnterpriseIdentity,
} from "./shared.js";
import { readWorksheetMatrix } from "./read-workbook.js";

const ROSTER_HEADERS = {
  clientUnit: "MV Client Unit",
  dailyWorkingHoursPerFte: "Daily Working Hours/FTE",
  department: "MV Org Unit 2 / Department",
  eid: "EID",
  firstDayInDispo: "First day in dispo",
  fte: "FTE",
  lastDayInDispo: "Last day in dispo",
  mainSkillset: "MV Main Skillset",
  managementLevel: "Management Level",
  managementLevelGroup: "Management Level Group",
  metroCity: "Metro City",
  rawChapter: "MV Org Unit 1 / Chapter",
  rawResourceType: "MV Ressource Type",
  resourceHoursPerWeek: "Resource Hours/Week",
  vacationDaysPerYear: "Vacation days / year",
} as const;

const SAP_HEADERS = {
  employeeEmail: "Employee Email",
  employeeName: "Employee Name",
  enterpriseId: "Enterprise ID",
  fte: "FTE",
  managementLevel: "Management Level",
  managementLevelGroup: "Management Level Group",
  metroCity: "Metro City",
  orgUnitLevel5: "Org Unit Level 5",
  orgUnitLevel6: "Org Unit Level 6",
  orgUnitLevel7: "Org Unit Level 7",
} as const;

interface RosterSourceRow {
  canonicalExternalId: string;
  clientUnitName: string | null;
  dailyWorkingHoursPerFte: number | null;
  department: string | null;
  fte: number | null;
  mainSkillset: string | null;
  managementLevelGroupName: string | null;
  managementLevelName: string | null;
  metroCityName: string | null;
  rawChapter: string | null;
  rawResourceType: string | null;
  resourceHoursPerWeek: number | null;
  rowNumber: number;
  vacationDaysPerYear: number | null;
  firstDayInDispo: Date | null;
  lastDayInDispo: Date | null;
}

interface SapSourceRow {
  canonicalExternalId: string;
  employeeEmail: string | null;
  employeeName: string | null;
  fte: number | null;
  managementLevelGroupName: string | null;
  managementLevelName: string | null;
  metroCityName: string | null;
  orgUnitLevelFive: string | null;
  orgUnitLevelSix: string | null;
  orgUnitLevelSeven: string | null;
  rowNumber: number;
}

interface ParseDispoRosterWorkbookOptions {
  costWorkbookPath?: string;
}

function buildHeaderMap(headerRow: ReadonlyArray<unknown>): Map<string, number> {
  const headerMap = new Map<string, number>();

  headerRow.forEach((value, index) => {
    const normalized = normalizeText(value);
    if (normalized) {
      headerMap.set(normalized, index);
    }
  });

  return headerMap;
}

function getCellValue(
  row: ReadonlyArray<unknown>,
  headerMap: Map<string, number>,
  headerName: string,
): unknown {
  const index = headerMap.get(headerName);
  if (index === undefined) {
    return null;
  }

  return row[index] ?? null;
}

function parseOptionalNumber(value: unknown): number | null {
  if (typeof value === "number" && Number.isFinite(value)) {
    return value;
  }

  const normalized = normalizeNullableWorkbookValue(value);
  if (!normalized) {
    return null;
  }

  const parsed = Number(normalized.replace(",", "."));
  return Number.isFinite(parsed) ? parsed : null;
}

function parseOptionalDate(value: unknown): Date | null {
  if (value instanceof Date && !Number.isNaN(value.valueOf())) {
    return value;
  }

  const normalized = normalizeNullableWorkbookValue(value);
  if (!normalized) {
    return null;
  }

  const parsed = new Date(normalized);
  return Number.isNaN(parsed.valueOf()) ? null : parsed;
}

function normalizeSapDisplayName(value: string | null): string | null {
  if (!value) {
    return null;
  }

  const normalized = value
    .split(",")
    .map((part) => normalizeText(part))
    .filter((part): part is string => Boolean(part));

  if (normalized.length === 2) {
    return `${normalized[1]} ${normalized[0]}`;
  }

  return normalizeText(value);
}

function buildResourceWarnings(
  resourceType: ResourceType,
  resourceTypeWarning: string | null,
  roster: RosterSourceRow | null,
  sap: SapSourceRow | null,
): string[] {
  const warnings: string[] = [];

  if (resourceTypeWarning) {
    warnings.push(resourceTypeWarning);
  }
  if (!roster) {
    warnings.push("Missing DispoRoster row; resource imported from SAP_data only");
  }
  if (!sap) {
    warnings.push("Missing SAP_data row; email and display name fall back to derived values");
  }
  if (resourceType === ResourceType.FREELANCER && !roster?.dailyWorkingHoursPerFte) {
    warnings.push("Freelancer row has no daily working hours value; defaulting to 8h/day");
  }

  return warnings;
}

function shouldExcludeImportedResource(resource: {
  canonicalExternalId: string;
  sourceEmail: string | null;
  managementLevelName: string | null;
}) {
  return !resource.sourceEmail && !resource.managementLevelName;
}

function applyRateResolution(input: {
  canonicalExternalId: string;
  level: string | null;
  rateRecord: ParsedResourceRosterRate | null;
  levelAverage: ParsedResourceRosterLevelAverage | null;
  warnings: string[];
}) {
  const { canonicalExternalId, level, rateRecord, levelAverage, warnings } = input;

  const exactLcr = rateRecord?.lcrCents ?? null;
  const exactUcr = rateRecord?.ucrCents ?? null;
  const fallbackLcr = levelAverage?.lcrCents ?? null;
  const fallbackUcr = levelAverage?.ucrCents ?? null;

  const lcrCents = exactLcr ?? fallbackLcr;
  const ucrCents = exactUcr ?? fallbackUcr;

  if (!rateRecord) {
    if (levelAverage && lcrCents !== null && ucrCents !== null) {
      warnings.push(
        `Applied level-average rates for ${canonicalExternalId} using management level ${levelAverage.level}`,
      );
      return {
        lcrCents,
        ucrCents,
        rateResolution: "LEVEL_AVERAGE" as const,
        rateResolutionLevel: levelAverage.level,
      };
    }

    warnings.push(
      level
        ? `Missing rate row for ${canonicalExternalId}; no usable level-average rate found for ${level}`
        : `Missing rate row for ${canonicalExternalId}; management level unavailable for fallback`,
    );
    return {
      lcrCents,
      ucrCents,
      rateResolution: "MISSING" as const,
      rateResolutionLevel: levelAverage?.level ?? level ?? null,
    };
  }

  if (exactLcr !== null && exactUcr !== null) {
    return {
      lcrCents,
      ucrCents,
      rateResolution: "EXACT" as const,
      rateResolutionLevel: rateRecord.level ?? null,
    };
  }

  if (levelAverage && lcrCents !== null && ucrCents !== null) {
    warnings.push(
      `Completed incomplete rate row for ${canonicalExternalId} with level-average rates from ${levelAverage.level}`,
    );
    return {
      lcrCents,
      ucrCents,
      rateResolution: "LEVEL_AVERAGE" as const,
      rateResolutionLevel: levelAverage.level,
    };
  }

  warnings.push(`Incomplete rate row for ${canonicalExternalId} could not be fully resolved`);
  return {
    lcrCents,
    ucrCents,
    rateResolution: "MISSING" as const,
    rateResolutionLevel: rateRecord.level ?? level ?? null,
  };
}

export async function parseDispoRosterWorkbook(
  workbookPath: string,
  options: ParseDispoRosterWorkbookOptions = {},
): Promise<ParsedRosterWorkbook> {
  const [rosterRows, sapRows] = await Promise.all([
    readWorksheetMatrix(workbookPath, DISPO_ROSTER_SHEET),
    readWorksheetMatrix(workbookPath, DISPO_ROSTER_SAP_SHEET),
  ]);
  const rateWorkbook: ParsedResourceRosterMasterWorkbook | null = options.costWorkbookPath
    ? await parseResourceRosterMasterWorkbook(options.costWorkbookPath)
    : null;
  const rosterHeaderMap = buildHeaderMap(rosterRows[0] ?? []);
  const sapHeaderMap = buildHeaderMap(sapRows[1] ?? []);

  const warnings: string[] = [...(rateWorkbook?.warnings ?? [])];
  const unresolved: ParsedUnresolvedRecord[] = [];
  const rosterById = new Map<string, RosterSourceRow>();
  const sapById = new Map<string, SapSourceRow>();
  let ignoredPseudoDemandRows = 0;

  for (let rowNumber = 2; rowNumber <= rosterRows.length; rowNumber += 1) {
    const row = rosterRows[rowNumber - 1] ?? [];
    const eidValue = normalizeNullableWorkbookValue(getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.eid));
    if (!eidValue) {
      if (row.some((value) => normalizeText(value) !== null)) {
        warnings.push(`Ignoring DispoRoster row ${rowNumber} because EID is missing`);
      }
      continue;
    }

    if (isPseudoDemandResourceIdentity(eidValue)) {
      ignoredPseudoDemandRows += 1;
      continue;
    }

    const canonicalExternalId = resolveCanonicalEnterpriseIdentity(eidValue);
    if (!canonicalExternalId) {
      unresolved.push({
        sourceRow: rowNumber,
        sourceColumn: "A",
        recordType: DispoStagedRecordType.RESOURCE,
        resourceExternalId: eidValue,
        message: `Unable to normalize EID "${eidValue}"`,
        resolutionHint: "Validate EID formatting in DispoRoster",
        warnings: [],
        normalizedData: { eid: eidValue },
      });
      continue;
    }

    if (rosterById.has(canonicalExternalId)) {
      unresolved.push({
        sourceRow: rowNumber,
        sourceColumn: "A",
        recordType: DispoStagedRecordType.RESOURCE,
        resourceExternalId: canonicalExternalId,
        message: `Duplicate DispoRoster row found for ${canonicalExternalId}`,
        resolutionHint: "Keep exactly one operational roster row per EID",
        warnings: [],
        normalizedData: { eid: canonicalExternalId },
      });
      continue;
    }

    rosterById.set(canonicalExternalId, {
      canonicalExternalId,
      rowNumber,
      metroCityName: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.metroCity),
      ),
      managementLevelGroupName: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.managementLevelGroup),
      ),
      managementLevelName: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.managementLevel),
      ),
      fte: parseOptionalNumber(getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.fte)),
      dailyWorkingHoursPerFte: parseOptionalNumber(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.dailyWorkingHoursPerFte),
      ),
      resourceHoursPerWeek: parseOptionalNumber(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.resourceHoursPerWeek),
      ),
      rawResourceType: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.rawResourceType),
      ),
      clientUnitName: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.clientUnit),
      ),
      rawChapter: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.rawChapter),
      ),
      department: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.department),
      ),
      mainSkillset: normalizeNullableWorkbookValue(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.mainSkillset),
      ),
      firstDayInDispo: parseOptionalDate(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.firstDayInDispo),
      ),
      lastDayInDispo: parseOptionalDate(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.lastDayInDispo),
      ),
      vacationDaysPerYear: parseOptionalNumber(
        getCellValue(row, rosterHeaderMap, ROSTER_HEADERS.vacationDaysPerYear),
      ),
    });
  }

  for (let rowNumber = 3; rowNumber <= sapRows.length; rowNumber += 1) {
    const row = sapRows[rowNumber - 1] ?? [];
    const enterpriseIdValue = normalizeNullableWorkbookValue(
      getCellValue(row, sapHeaderMap, SAP_HEADERS.enterpriseId),
    );
    if (!enterpriseIdValue) {
      continue;
    }

    const canonicalExternalId = resolveCanonicalEnterpriseIdentity(enterpriseIdValue);
    if (!canonicalExternalId) {
      unresolved.push({
        sourceRow: rowNumber,
        sourceColumn: "C",
        recordType: DispoStagedRecordType.RESOURCE,
        resourceExternalId: enterpriseIdValue,
        message: `Unable to normalize Enterprise ID "${enterpriseIdValue}"`,
        resolutionHint: "Validate Enterprise ID formatting in SAP_data",
        warnings: [],
        normalizedData: { enterpriseId: enterpriseIdValue },
      });
      continue;
    }

    if (sapById.has(canonicalExternalId)) {
      unresolved.push({
        sourceRow: rowNumber,
        sourceColumn: "C",
        recordType: DispoStagedRecordType.RESOURCE,
        resourceExternalId: canonicalExternalId,
        message: `Duplicate SAP_data row found for ${canonicalExternalId}`,
        resolutionHint: "Keep exactly one SAP_data row per Enterprise ID",
        warnings: [],
        normalizedData: { enterpriseId: canonicalExternalId },
      });
      continue;
    }

    sapById.set(canonicalExternalId, {
      canonicalExternalId,
      rowNumber,
      employeeName: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.employeeName),
      ),
      employeeEmail: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.employeeEmail),
      )?.toLowerCase() ?? null,
      metroCityName: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.metroCity),
      ),
      managementLevelGroupName: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.managementLevelGroup),
      ),
      managementLevelName: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.managementLevel),
      ),
      orgUnitLevelFive: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.orgUnitLevel5),
      ),
      orgUnitLevelSix: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.orgUnitLevel6),
      ),
      orgUnitLevelSeven: normalizeNullableWorkbookValue(
        getCellValue(row, sapHeaderMap, SAP_HEADERS.orgUnitLevel7),
      ),
      fte: parseOptionalNumber(getCellValue(row, sapHeaderMap, SAP_HEADERS.fte)),
    });
  }

  const resourceIds = new Set<string>([...rosterById.keys(), ...sapById.keys()]);
  const resources: ParsedRosterResource[] = [];
  const excludedCanonicalExternalIds = new Set<string>();

  for (const canonicalExternalId of resourceIds) {
    const roster = rosterById.get(canonicalExternalId) ?? null;
    const sap = sapById.get(canonicalExternalId) ?? null;
    const roleTokens = deriveRoleTokens(
      roster?.department,
      roster?.rawChapter,
      roster?.mainSkillset,
      sap?.orgUnitLevelSix,
      sap?.orgUnitLevelSeven,
    );
    const normalizedChapter = deriveNormalizedChapter(roster?.rawChapter ?? null, roleTokens);
    const resourceTypeResult = mapChargeabilityResourceType(roster?.rawResourceType ?? null);
    const resourceType =
      roster?.rawResourceType || sap ? resourceTypeResult.resourceType : ResourceType.EMPLOYEE;
    const fte = sap?.fte ?? roster?.fte ?? null;
    const dailyWorkingHoursPerFte = roster?.dailyWorkingHoursPerFte ?? null;
    const displayName =
      normalizeSapDisplayName(sap?.employeeName ?? null) ??
      deriveDisplayNameFromEnterpriseId(canonicalExternalId);
    const metroCityName = sap?.metroCityName ?? roster?.metroCityName ?? null;
    const managementLevelName =
      sap?.managementLevelName ?? roster?.managementLevelName ?? null;
    const resourceWarnings = buildResourceWarnings(resourceType, resourceTypeResult.warning, roster, sap);
    const rateResolution = applyRateResolution({
      canonicalExternalId,
      level: managementLevelName,
      rateRecord: rateWorkbook?.rates.get(canonicalExternalId) ?? null,
      levelAverage: managementLevelName
        ? rateWorkbook?.levelAverages.get(managementLevelName) ?? null
        : null,
      warnings: resourceWarnings,
    });

    const resource: ParsedRosterResource = {
      sourceRow: roster?.rowNumber ?? sap?.rowNumber ?? 0,
      sourceSheet: roster ? DISPO_ROSTER_SHEET : DISPO_ROSTER_SAP_SHEET,
      canonicalExternalId,
      enterpriseId: canonicalExternalId,
      eid: canonicalExternalId,
      displayName,
      email: sap?.employeeEmail ?? buildFallbackAccentureEmail(canonicalExternalId),
      chapter: normalizedChapter.chapter,
      chapterCode: normalizedChapter.chapterCode,
      managementLevelGroupName: sap?.managementLevelGroupName ?? roster?.managementLevelGroupName ?? null,
      managementLevelName,
      countryCode: deriveCountryCodeFromMetroCity(metroCityName),
      metroCityName,
      clientUnitName: roster?.clientUnitName ?? null,
      rawResourceType: roster?.rawResourceType ?? null,
      resourceType,
      fte,
      lcrCents: rateResolution.lcrCents,
      ucrCents: rateResolution.ucrCents,
      rateResolution: rateResolution.rateResolution,
      rateResolutionLevel: rateResolution.rateResolutionLevel,
      availability: createWeekdayAvailabilityFromFte(
        fte ?? 1,
        dailyWorkingHoursPerFte ?? 8,
      ) as unknown as ParsedRosterResource["availability"],
      roleTokens,
      dailyWorkingHoursPerFte,
      department: roster?.department ?? null,
      mainSkillset: roster?.mainSkillset ?? null,
      resourceHoursPerWeek: roster?.resourceHoursPerWeek ?? null,
      firstDayInDispo: roster?.firstDayInDispo ?? null,
      lastDayInDispo: roster?.lastDayInDispo ?? null,
      vacationDaysPerYear: roster?.vacationDaysPerYear ?? null,
      sapEmployeeName: sap?.employeeName ?? null,
      sapOrgUnitLevelFive: sap?.orgUnitLevelFive ?? null,
      sapOrgUnitLevelSix: sap?.orgUnitLevelSix ?? null,
      sapOrgUnitLevelSeven: sap?.orgUnitLevelSeven ?? null,
      warnings: resourceWarnings,
    };

    if (
      shouldExcludeImportedResource({
        canonicalExternalId,
        sourceEmail: sap?.employeeEmail ?? null,
        managementLevelName,
      })
    ) {
      excludedCanonicalExternalIds.add(canonicalExternalId);
      warnings.push(
        `Excluded ${canonicalExternalId} from import because neither email nor management level is present in the supplied sources`,
      );
      continue;
    }

    resources.push(resource);
  }

  if (ignoredPseudoDemandRows > 0) {
    warnings.push(`Ignored ${ignoredPseudoDemandRows} pseudo-demand rows from DispoRoster`);
  }

  resources.sort((left, right) => left.canonicalExternalId.localeCompare(right.canonicalExternalId));

  return {
    excludedCanonicalExternalIds: Array.from(excludedCanonicalExternalIds).sort((left, right) =>
      left.localeCompare(right),
    ),
    resources,
    unresolved,
    warnings,
    ignoredPseudoDemandRows,
  };
}
