import { normalizeCanonicalResourceIdentity } from "@capakraken/shared";
import { readWorksheetMatrix } from "./read-workbook.js";
import { normalizeNullableWorkbookValue, normalizeText } from "./shared.js";

const RESOURCE_ROSTER_MASTER_SHEET = "Dispo Namen";

const HEADERS = {
  chapter: "Chapter",
  employeeName: "Mitarbeiter (laut Dispo)",
  experience: "Experience",
  fte: "FTE",
  lcr: "LCR (EUR)",
  level: "Level",
  location: "Location",
  status: "Status",
  typeOfWork: "Type of work",
  ucr: "UCR (EUR)",
} as const;

export interface ParsedResourceRosterRate {
  canonicalExternalId: string;
  chapter: string | null;
  experience: string | null;
  fte: number | null;
  lcrCents: number | null;
  level: string | null;
  location: string | null;
  sourceRow: number;
  status: string | null;
  typeOfWork: string | null;
  ucrCents: number | null;
  warnings: string[];
}

export interface ParsedResourceRosterLevelAverage {
  lcrCents: number | null;
  level: string;
  sampleCount: number;
  ucrCents: number | null;
}

export interface ParsedResourceRosterMasterWorkbook {
  levelAverages: Map<string, ParsedResourceRosterLevelAverage>;
  rates: Map<string, ParsedResourceRosterRate>;
  warnings: 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 toCents(value: number | null): number | null {
  return value === null ? null : Math.round(value * 100);
}

export async function parseResourceRosterMasterWorkbook(
  workbookPath: string,
): Promise<ParsedResourceRosterMasterWorkbook> {
  const rows = await readWorksheetMatrix(workbookPath, RESOURCE_ROSTER_MASTER_SHEET);
  const headerMap = buildHeaderMap(rows[0] ?? []);
  const warnings: string[] = [];
  const rates = new Map<string, ParsedResourceRosterRate>();
  const levelBuckets = new Map<string, { lcr: number[]; ucr: number[] }>();

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

    if (!employeeName) {
      continue;
    }

    const canonicalExternalId = normalizeCanonicalResourceIdentity(employeeName);
    const level = normalizeNullableWorkbookValue(getCellValue(row, headerMap, HEADERS.level));
    const ucrValue = parseOptionalNumber(getCellValue(row, headerMap, HEADERS.ucr));
    const lcrValue = parseOptionalNumber(getCellValue(row, headerMap, HEADERS.lcr));
    const recordWarnings: string[] = [];

    if (rates.has(canonicalExternalId)) {
      recordWarnings.push(`Duplicate rate row ${rowNumber} ignored for ${canonicalExternalId}`);
      warnings.push(recordWarnings[0] ?? `Duplicate rate row ${rowNumber} ignored`);
      continue;
    }

    if (ucrValue === null || lcrValue === null) {
      recordWarnings.push(`Incomplete rate row for ${canonicalExternalId}`);
    }

    rates.set(canonicalExternalId, {
      canonicalExternalId,
      sourceRow: rowNumber,
      ucrCents: toCents(ucrValue),
      lcrCents: toCents(lcrValue),
      fte: parseOptionalNumber(getCellValue(row, headerMap, HEADERS.fte)),
      level,
      typeOfWork: normalizeNullableWorkbookValue(getCellValue(row, headerMap, HEADERS.typeOfWork)),
      chapter: normalizeNullableWorkbookValue(getCellValue(row, headerMap, HEADERS.chapter)),
      location: normalizeNullableWorkbookValue(getCellValue(row, headerMap, HEADERS.location)),
      status: normalizeNullableWorkbookValue(getCellValue(row, headerMap, HEADERS.status)),
      experience: normalizeNullableWorkbookValue(getCellValue(row, headerMap, HEADERS.experience)),
      warnings: recordWarnings,
    });

    if (level) {
      const bucket = levelBuckets.get(level) ?? { lcr: [], ucr: [] };
      if (lcrValue !== null) {
        bucket.lcr.push(lcrValue);
      }
      if (ucrValue !== null) {
        bucket.ucr.push(ucrValue);
      }
      levelBuckets.set(level, bucket);
    }
  }

  const levelAverages = new Map<string, ParsedResourceRosterLevelAverage>();
  for (const [level, bucket] of levelBuckets.entries()) {
    const lcrAverage =
      bucket.lcr.length > 0
        ? Math.round((bucket.lcr.reduce((sum, value) => sum + value, 0) / bucket.lcr.length) * 100)
        : null;
    const ucrAverage =
      bucket.ucr.length > 0
        ? Math.round((bucket.ucr.reduce((sum, value) => sum + value, 0) / bucket.ucr.length) * 100)
        : null;

    levelAverages.set(level, {
      level,
      sampleCount: Math.max(bucket.lcr.length, bucket.ucr.length),
      lcrCents: lcrAverage,
      ucrCents: ucrAverage,
    });
  }

  return {
    rates,
    levelAverages,
    warnings,
  };
}
