import type { WeekdayAvailability } from "@capakraken/shared";
import {
  createWeekdayAvailabilityFromFte,
  normalizeDispoRoleToken,
} from "@capakraken/shared";
import type { TxClient, MergedStagedResource } from "./commit-dispo-batch-types.js";
import { deriveRoleTokens } from "./shared.js";

function asNullableString(value: unknown): string | null {
  return typeof value === "string" && value.trim().length > 0 ? value : null;
}

function normalizeFallbackRoleName(value: string): string {
  return value.replace(/\s+/g, " ").trim();
}

function isFiniteNumber(value: unknown): value is number {
  return typeof value === "number" && Number.isFinite(value);
}

function asObject(value: unknown): Record<string, unknown> {
  return value && typeof value === "object" && !Array.isArray(value)
    ? (value as Record<string, unknown>)
    : {};
}

function mergeScalar<T>(
  current: T | null,
  incoming: T | null | undefined,
): T | null {
  return incoming ?? current;
}

export function inferRoleNameFromResource(resource: MergedStagedResource): string | null {
  const explicitRoleName = Array.from(resource.roleTokens)
    .map((token) => normalizeDispoRoleToken(token))
    .find((roleName): roleName is string => Boolean(roleName));
  if (explicitRoleName) {
    return explicitRoleName;
  }

  const derivedRoleName = deriveRoleTokens(
    resource.chapter,
    asNullableString(resource.rawPayload.department),
    asNullableString(resource.rawPayload.mainSkillset),
    asNullableString(resource.rawPayload.sapOrgUnitLevelSix),
    asNullableString(resource.rawPayload.sapOrgUnitLevelSeven),
    asNullableString(resource.rawPayload.sapEmployeeName),
  )
    .map((token) => normalizeDispoRoleToken(token))
    .find((roleName): roleName is string => Boolean(roleName));
  if (derivedRoleName) {
    return derivedRoleName;
  }

  if (resource.chapter === "Art Direction") {
    return "Art Director";
  }
  if (resource.chapter === "Project Management") {
    return "Project Manager";
  }

  const fallbackRoleLabel =
    asNullableString(resource.rawPayload.department) ??
    asNullableString(resource.rawPayload.mainSkillset) ??
    asNullableString(resource.chapter) ??
    asNullableString(resource.rawPayload.sapOrgUnitLevelSeven) ??
    asNullableString(resource.rawPayload.sapOrgUnitLevelSix);

  return fallbackRoleLabel ? normalizeFallbackRoleName(fallbackRoleLabel) : null;
}

export function mergeStagedResources(
  rows: Awaited<ReturnType<TxClient["stagedResource"]["findMany"]>>,
): Map<string, MergedStagedResource> {
  const sourcePriority = new Map([
    ["CHARGEABILITY", 1],
    ["ROSTER", 2],
  ]);
  const ordered = [...rows].sort(
    (left, right) =>
      (sourcePriority.get(left.sourceKind) ?? 0) - (sourcePriority.get(right.sourceKind) ?? 0),
  );
  const merged = new Map<string, MergedStagedResource>();

  for (const row of ordered) {
    const existing = merged.get(row.canonicalExternalId);
    const rawPayload = asObject(row.rawPayload);

    if (!existing) {
      merged.set(row.canonicalExternalId, {
        availability: row.availability ?? null,
        canonicalExternalId: row.canonicalExternalId,
        chapter: row.chapter ?? null,
        chargeabilityTarget: row.chargeabilityTarget ?? null,
        clientUnitName: row.clientUnitName ?? null,
        countryCode: row.countryCode ?? null,
        displayName: row.displayName ?? null,
        email: row.email ?? null,
        fte: row.fte ?? null,
        lcrCents: row.lcrCents ?? null,
        managementLevelGroupName: row.managementLevelGroupName ?? null,
        managementLevelName: row.managementLevelName ?? null,
        metroCityName: row.metroCityName ?? null,
        rawPayload,
        resourceType: row.resourceType ?? null,
        roleTokens: new Set(row.roleTokens),
        sourceKinds: [row.sourceKind],
        ucrCents: row.ucrCents ?? null,
        vacationDaysPerYear: isFiniteNumber(rawPayload.vacationDaysPerYear)
          ? rawPayload.vacationDaysPerYear
          : null,
        warnings: [...row.warnings],
      });
      continue;
    }

    existing.availability = mergeScalar(existing.availability, row.availability);
    existing.chapter = mergeScalar(existing.chapter, row.chapter);
    existing.chargeabilityTarget = mergeScalar(existing.chargeabilityTarget, row.chargeabilityTarget);
    existing.clientUnitName = mergeScalar(existing.clientUnitName, row.clientUnitName);
    existing.countryCode = mergeScalar(existing.countryCode, row.countryCode);
    existing.displayName = mergeScalar(existing.displayName, row.displayName);
    existing.email = mergeScalar(existing.email, row.email);
    existing.fte = mergeScalar(existing.fte, row.fte);
    existing.lcrCents = mergeScalar(existing.lcrCents, row.lcrCents);
    existing.managementLevelGroupName = mergeScalar(
      existing.managementLevelGroupName,
      row.managementLevelGroupName,
    );
    existing.managementLevelName = mergeScalar(existing.managementLevelName, row.managementLevelName);
    existing.metroCityName = mergeScalar(existing.metroCityName, row.metroCityName);
    existing.resourceType = mergeScalar(existing.resourceType, row.resourceType);
    existing.ucrCents = mergeScalar(existing.ucrCents, row.ucrCents);
    if (existing.availability === null && row.availability !== null) {
      existing.availability = row.availability;
    }
    for (const roleToken of row.roleTokens) {
      existing.roleTokens.add(roleToken);
    }
    existing.sourceKinds.push(row.sourceKind);
    existing.warnings.push(...row.warnings);
    existing.rawPayload = {
      ...existing.rawPayload,
      ...rawPayload,
    };
    if (isFiniteNumber(rawPayload.vacationDaysPerYear)) {
      existing.vacationDaysPerYear = rawPayload.vacationDaysPerYear;
    }
  }

  return merged;
}

export function parseWeekdayAvailability(
  value: unknown,
  fallbackFte: number | null,
): WeekdayAvailability {
  const fallback = createWeekdayAvailabilityFromFte(fallbackFte ?? 1);
  const source = asObject(value);

  return {
    monday: isFiniteNumber(source.monday) ? source.monday : fallback.monday,
    tuesday: isFiniteNumber(source.tuesday) ? source.tuesday : fallback.tuesday,
    wednesday: isFiniteNumber(source.wednesday) ? source.wednesday : fallback.wednesday,
    thursday: isFiniteNumber(source.thursday) ? source.thursday : fallback.thursday,
    friday: isFiniteNumber(source.friday) ? source.friday : fallback.friday,
  };
}

export interface ReferenceDataMaps {
  clientIdByCode: Map<string, string>;
  clientIdByName: Map<string, string>;
  countryIdByCode: Map<string, string>;
  managementLevelGroupByName: Map<string, { id: string; name: string; targetPercentage: number }>;
  managementLevelIdByName: Map<string, string>;
  metroCityIdByName: Map<string, string>;
  orgUnitIdByLevelAndName: Map<string, string>;
  roleIdByName: Map<string, string>;
  utilizationCategoryIdByCode: Map<string, string>;
}

export function buildReferenceDataMaps(data: {
  clients: { id: string; code: string | null; name: string }[];
  countries: { id: string; code: string }[];
  managementLevelGroups: { id: string; name: string; targetPercentage: number }[];
  managementLevels: { id: string; name: string }[];
  metroCities: { id: string; name: string }[];
  orgUnits: { id: string; level: number; name: string }[];
  roles: { id: string; name: string }[];
  utilizationCategories: { id: string; code: string }[];
}): ReferenceDataMaps {
  return {
    clientIdByCode: new Map(
      data.clients.filter((client) => client.code).map((client) => [client.code!, client.id]),
    ),
    clientIdByName: new Map(
      data.clients.map((client) => [client.name.toLowerCase(), client.id]),
    ),
    countryIdByCode: new Map(
      data.countries.map((country) => [country.code, country.id]),
    ),
    managementLevelGroupByName: new Map(
      data.managementLevelGroups.map((group) => [group.name, group]),
    ),
    managementLevelIdByName: new Map(
      data.managementLevels.map((level) => [level.name, level.id]),
    ),
    metroCityIdByName: new Map(
      data.metroCities.map((metroCity) => [metroCity.name.toLowerCase(), metroCity.id]),
    ),
    orgUnitIdByLevelAndName: new Map(
      data.orgUnits.map((orgUnit) => [`${orgUnit.level}:${orgUnit.name.toLowerCase()}`, orgUnit.id]),
    ),
    roleIdByName: new Map(
      data.roles.map((role) => [role.name, role.id]),
    ),
    utilizationCategoryIdByCode: new Map(
      data.utilizationCategories.map((category) => [category.code, category.id]),
    ),
  };
}
