import { DispoStagedRecordType } from "@capakraken/db";
import { DISPO_CHARGEABILITY_SHEET, type ParsedChargeabilityResource, type ParsedChargeabilityWorkbook, type ParsedUnresolvedRecord, buildFallbackAccentureEmail, createAvailabilityFromFte, deriveCountryCodeFromMetroCity, deriveDisplayNameFromEnterpriseId, deriveNormalizedChapter, deriveRoleTokens, ensurePercentageValue, mapChargeabilityResourceType, normalizeNullableWorkbookValue, normalizeText, resolveCanonicalEnterpriseIdentity } from "./shared.js";
import { readWorksheetMatrix } from "./read-workbook.js";

const CHGFC_HEADERS = {
  clientUnit: "MV Client Unit",
  enterpriseId: "Enterprise ID",
  fte: "FTE",
  managementLevelGroup: "Management Level Group",
  metroCity: "Metro City",
  orgUnitLevel6: "Org Unit Level 6",
  rawChapter: "MV Org Unit 1 / Chapter",
  rawResourceType: "MV Ressource Type",
  target: "Target (per Level)",
} as const;

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 buildResourceSignature(resource: ParsedChargeabilityResource): string {
  return JSON.stringify({
    chapter: resource.chapter,
    chapterCode: resource.chapterCode,
    chargeabilityTarget: resource.chargeabilityTarget,
    clientUnitName: resource.clientUnitName,
    countryCode: resource.countryCode,
    fte: resource.fte,
    managementLevelGroupName: resource.managementLevelGroupName,
    metroCityName: resource.metroCityName,
    resourceType: resource.resourceType,
    roleTokens: resource.roleTokens,
  });
}

export async function parseDispoChargeabilityWorkbook(
  workbookPath: string,
): Promise<ParsedChargeabilityWorkbook> {
  const rows = await readWorksheetMatrix(workbookPath, DISPO_CHARGEABILITY_SHEET);
  const headerMap = buildHeaderMap(rows[0] ?? []);
  const warnings: string[] = [];
  const unresolved: ParsedUnresolvedRecord[] = [];
  const resourceByCanonicalId = new Map<string, ParsedChargeabilityResource>();

  for (let rowNumber = 2; rowNumber <= rows.length; rowNumber += 1) {
    const row = rows[rowNumber - 1] ?? [];
    const enterpriseIdValue = normalizeNullableWorkbookValue(
      getCellValue(row, headerMap, CHGFC_HEADERS.enterpriseId),
    );

    if (!enterpriseIdValue) {
      if (row.some((value) => normalizeText(value) !== null)) {
        unresolved.push({
          sourceRow: rowNumber,
          sourceColumn: "A",
          recordType: DispoStagedRecordType.RESOURCE,
          resourceExternalId: null,
          message: "Missing Enterprise ID in ChgFC row",
          resolutionHint: "Populate Enterprise ID before staging resource data",
          warnings: [],
          normalizedData: {},
        });
      }
      continue;
    }

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

    const managementLevelGroupName = normalizeNullableWorkbookValue(
      getCellValue(row, headerMap, CHGFC_HEADERS.managementLevelGroup),
    );
    const rawTarget = getCellValue(row, headerMap, CHGFC_HEADERS.target);
    const fte = typeof getCellValue(row, headerMap, CHGFC_HEADERS.fte) === "number"
      ? Number(getCellValue(row, headerMap, CHGFC_HEADERS.fte))
      : null;
    const metroCityName = normalizeNullableWorkbookValue(
      getCellValue(row, headerMap, CHGFC_HEADERS.metroCity),
    );
    const rawResourceType = normalizeNullableWorkbookValue(
      getCellValue(row, headerMap, CHGFC_HEADERS.rawResourceType),
    );
    const levelSixName = normalizeNullableWorkbookValue(
      getCellValue(row, headerMap, CHGFC_HEADERS.orgUnitLevel6),
    );
    const rawChapter = normalizeNullableWorkbookValue(
      getCellValue(row, headerMap, CHGFC_HEADERS.rawChapter),
    );
    const clientUnitName = normalizeNullableWorkbookValue(
      getCellValue(row, headerMap, CHGFC_HEADERS.clientUnit),
    );

    const roleTokens = deriveRoleTokens(levelSixName, rawChapter);
    const normalizedChapter = deriveNormalizedChapter(rawChapter, roleTokens);
    const resourceTypeResult = mapChargeabilityResourceType(rawResourceType);
    const recordWarnings = resourceTypeResult.warning ? [resourceTypeResult.warning] : [];
    const chargeabilityTarget =
      typeof rawTarget === "number" ? ensurePercentageValue(rawTarget) : null;

    const resource: ParsedChargeabilityResource = {
      sourceRow: rowNumber,
      canonicalExternalId,
      enterpriseId: canonicalExternalId,
      eid: canonicalExternalId,
      displayName: deriveDisplayNameFromEnterpriseId(canonicalExternalId),
      email: buildFallbackAccentureEmail(canonicalExternalId),
      chapter: normalizedChapter.chapter,
      chapterCode: normalizedChapter.chapterCode,
      managementLevelGroupName,
      managementLevelName: null,
      countryCode: deriveCountryCodeFromMetroCity(metroCityName),
      metroCityName,
      clientUnitName,
      rawResourceType,
      resourceType: resourceTypeResult.resourceType,
      chargeabilityTarget,
      fte,
      availability: createAvailabilityFromFte(fte),
      roleTokens,
      warnings: recordWarnings,
    };

    const existing = resourceByCanonicalId.get(canonicalExternalId);
    if (!existing) {
      resourceByCanonicalId.set(canonicalExternalId, resource);
      continue;
    }

    const existingSignature = buildResourceSignature(existing);
    const nextSignature = buildResourceSignature(resource);

    if (existingSignature === nextSignature) {
      existing.warnings.push(`Duplicate ChgFC row ${rowNumber} ignored for ${canonicalExternalId}`);
      continue;
    }

    existing.warnings.push(`Conflicting duplicate ChgFC row ${rowNumber} found for ${canonicalExternalId}`);
    unresolved.push({
      sourceRow: rowNumber,
      sourceColumn: "A",
      recordType: DispoStagedRecordType.RESOURCE,
      resourceExternalId: canonicalExternalId,
      message: `Conflicting resource roster rows found for ${canonicalExternalId}`,
      resolutionHint: "Resolve the differing ChgFC roster values before commit",
      warnings: [...recordWarnings],
      normalizedData: {
        existing: {
          sourceRow: existing.sourceRow,
          chapter: existing.chapter,
          clientUnitName: existing.clientUnitName,
          fte: existing.fte,
          metroCityName: existing.metroCityName,
        },
        conflicting: {
          sourceRow: resource.sourceRow,
          chapter: resource.chapter,
          clientUnitName: resource.clientUnitName,
          fte: resource.fte,
          metroCityName: resource.metroCityName,
        },
      },
    });
  }

  return {
    resources: Array.from(resourceByCanonicalId.values()),
    unresolved,
    warnings,
  };
}
