import path from "node:path";
import type { Prisma, PrismaClient } from "@capakraken/db";
import {
  DispoImportSourceKind,
  DispoStagedRecordType,
  ImportBatchStatus,
  ResourceType,
  StagedRecordStatus,
} from "@capakraken/db";
import {
  createWeekdayAvailabilityFromFte,
  normalizeCanonicalResourceIdentity,
  normalizeDispoChapterToken,
} from "@capakraken/shared";

export type DispoImportDbClient = Pick<
  PrismaClient,
  | "client"
  | "country"
  | "importBatch"
  | "managementLevel"
  | "managementLevelGroup"
  | "metroCity"
  | "orgUnit"
  | "stagedAssignment"
  | "stagedAvailabilityRule"
  | "stagedClient"
  | "stagedProject"
  | "stagedResource"
  | "stagedVacation"
  | "stagedUnresolvedRecord"
>;

export interface DispoReferenceImportInput {
  importBatchId?: string;
  notes?: string | null;
  referenceWorkbookPath: string;
}

export interface DispoChargeabilityImportInput {
  chargeabilityWorkbookPath: string;
  excludedResourceExternalIds?: string[];
  importBatchId?: string;
  notes?: string | null;
}

export interface DispoRosterImportInput {
  costWorkbookPath?: string;
  importBatchId?: string;
  notes?: string | null;
  rosterWorkbookPath: string;
}

export interface DispoPlanningImportInput {
  excludedResourceExternalIds?: string[];
  importBatchId?: string;
  notes?: string | null;
  planningWorkbookPath: string;
}

export interface ParsedCountryReference {
  sourceRow: number;
  countryCode: string;
  name: string;
  dailyWorkingHours: number;
  metroCities: string[];
  scheduleRules?: Prisma.InputJsonValue;
}

export interface ParsedOrgUnitReference {
  sourceRow: number;
  level: number;
  name: string;
  parentName: string | null;
  sortOrder: number;
}

export interface ParsedManagementLevelGroupReference {
  sourceRow: number;
  name: string;
  targetPercentage: number;
  sortOrder: number;
  levels: string[];
}

export interface ParsedClientReference {
  sourceColumn: string;
  sourceRow: number;
  clientCode: string | null;
  name: string;
  parentClientCode: string | null;
  parentName: string | null;
  sortOrder: number;
}

export interface ParsedReferenceWorkbook {
  clients: ParsedClientReference[];
  countries: ParsedCountryReference[];
  managementLevelGroups: ParsedManagementLevelGroupReference[];
  orgUnits: ParsedOrgUnitReference[];
  warnings: string[];
}

export interface ParsedChargeabilityResource {
  availability: Prisma.InputJsonValue;
  canonicalExternalId: string;
  chapter: string | null;
  chapterCode: string | null;
  chargeabilityTarget: number | null;
  clientUnitName: string | null;
  countryCode: string | null;
  displayName: string;
  eid: string;
  email: string | null;
  enterpriseId: string;
  fte: number | null;
  managementLevelGroupName: string | null;
  managementLevelName: string | null;
  metroCityName: string | null;
  rawResourceType: string | null;
  resourceType: ResourceType;
  roleTokens: string[];
  sourceRow: number;
  warnings: string[];
}

export interface ParsedUnresolvedRecord {
  message: string;
  normalizedData: Record<string, unknown>;
  projectKey?: string | null;
  recordType: DispoStagedRecordType;
  resolutionHint?: string | null;
  resourceExternalId?: string | null;
  sourceColumn?: string | null;
  sourceRow: number;
  warnings: string[];
}

export interface ParsedChargeabilityWorkbook {
  resources: ParsedChargeabilityResource[];
  unresolved: ParsedUnresolvedRecord[];
  warnings: string[];
}

export interface ParsedRosterResource {
  availability: Prisma.InputJsonValue;
  canonicalExternalId: string;
  chapter: string | null;
  chapterCode: string | null;
  clientUnitName: string | null;
  countryCode: string | null;
  dailyWorkingHoursPerFte: number | null;
  department: string | null;
  displayName: string;
  eid: string;
  email: string | null;
  enterpriseId: string;
  firstDayInDispo: Date | null;
  fte: number | null;
  lastDayInDispo: Date | null;
  lcrCents: number | null;
  mainSkillset: string | null;
  managementLevelGroupName: string | null;
  managementLevelName: string | null;
  metroCityName: string | null;
  rawResourceType: string | null;
  resourceHoursPerWeek: number | null;
  resourceType: ResourceType;
  rateResolution: "EXACT" | "LEVEL_AVERAGE" | "MISSING";
  rateResolutionLevel: string | null;
  roleTokens: string[];
  sapEmployeeName: string | null;
  sapOrgUnitLevelFive: string | null;
  sapOrgUnitLevelSix: string | null;
  sapOrgUnitLevelSeven: string | null;
  sourceRow: number;
  sourceSheet: string;
  ucrCents: number | null;
  vacationDaysPerYear: number | null;
  warnings: string[];
}

export interface ParsedRosterWorkbook {
  excludedCanonicalExternalIds: string[];
  ignoredPseudoDemandRows: number;
  resources: ParsedRosterResource[];
  unresolved: ParsedUnresolvedRecord[];
  warnings: string[];
}

export interface ParsedPlanningAssignment {
  assignmentDate: Date;
  chapterToken: string | null;
  hoursPerDay: number;
  isInternal: boolean;
  isTbd: boolean;
  isUnassigned: boolean;
  percentage: number;
  projectKey: string | null;
  rawToken: string;
  resourceExternalId: string;
  roleName: string | null;
  roleToken: string | null;
  slotFraction: number;
  sourceColumn: string;
  sourceRow: number;
  utilizationCategoryCode: string | null;
  warnings: string[];
  winProbability: number | null;
}

export interface ParsedPlanningVacation {
  endDate: Date;
  halfDayPart: string | null;
  holidayName: string | null;
  isHalfDay: boolean;
  isPublicHoliday: boolean;
  note: string | null;
  rawToken: string;
  resourceExternalId: string;
  sourceColumn: string;
  sourceRow: number;
  startDate: Date;
  vacationType: "ANNUAL" | "OTHER" | "PUBLIC_HOLIDAY" | "SICK";
  warnings: string[];
}

export interface ParsedPlanningAvailabilityRule {
  availableHours: number | null;
  effectiveEndDate: Date | null;
  effectiveStartDate: Date | null;
  isResolved: boolean;
  percentage: number | null;
  rawToken: string;
  resourceExternalId: string;
  ruleType: string;
  sourceColumn: string;
  sourceRow: number;
  warnings: string[];
}

export interface ParsedPlanningWorkbook {
  assignments: ParsedPlanningAssignment[];
  availabilityRules: ParsedPlanningAvailabilityRule[];
  unresolved: ParsedUnresolvedRecord[];
  vacations: ParsedPlanningVacation[];
  warnings: string[];
}

const COUNTRY_REFERENCE_CONFIG = {
  "Costa Rica": {
    code: "CR",
    dailyWorkingHours: 8,
  },
  Germany: {
    code: "DE",
    dailyWorkingHours: 8,
  },
  Hungary: {
    code: "HU",
    dailyWorkingHours: 8,
  },
  India: {
    code: "IN",
    dailyWorkingHours: 9,
  },
  Italy: {
    code: "IT",
    dailyWorkingHours: 8,
  },
  Portugal: {
    code: "PT",
    dailyWorkingHours: 8,
  },
  Spain: {
    code: "ES",
    dailyWorkingHours: 8,
    scheduleRules: {
      type: "spain",
      fridayHours: 6.5,
      summerPeriod: { from: "07-01", to: "09-15" },
      summerHours: 6.5,
      regularHours: 9,
    },
  },
  "United Kingdom": {
    code: "GB",
    dailyWorkingHours: 8,
  },
} as const satisfies Record<
  string,
  {
    code: string;
    dailyWorkingHours: number;
    scheduleRules?: Prisma.InputJsonValue;
  }
>;

const CLIENT_CODE_OVERRIDES = {
  BMW: "BMW",
  DAIMLER: "DAIMLER",
  "EXOR-STELLANTIS": "STELLANTIS",
  VOLKSWAGEN: "VW",
  "TATA MOTORS GROUP": "JLR",
} as const satisfies Record<string, string>;

const NULLISH_TOKENS = new Set(["", "-", "0", "(Blank)"]);

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

export function normalizeText(value: unknown): string | null {
  if (value === null || value === undefined) {
    return null;
  }

  const normalized = collapseWhitespace(String(value));
  return normalized.length > 0 ? normalized : null;
}

export function normalizeNullableWorkbookValue(value: unknown): string | null {
  const normalized = normalizeText(value);
  if (!normalized) {
    return null;
  }

  return NULLISH_TOKENS.has(normalized) ? null : normalized;
}

export function buildFallbackAccentureEmail(canonicalExternalId: string): string {
  return `${canonicalExternalId}@accenture.com`;
}

export function isPseudoDemandResourceIdentity(value: string | null | undefined): boolean {
  return typeof value === "string" && value.toLowerCase().startsWith("demand_");
}

export function sanitizeClientName(value: string): string {
  return collapseWhitespace(value.replace(/\s*-\s*$/, ""));
}

export function getWorkbookFileName(workbookPath: string): string {
  return path.basename(workbookPath);
}

export function findSectionRow(
  rows: ReadonlyArray<ReadonlyArray<unknown>>,
  firstCellValue: string,
): number {
  const normalizedTarget = firstCellValue.toLowerCase();

  for (let index = 0; index < rows.length; index += 1) {
    const current = normalizeText(rows[index]?.[0]);
    if (current?.toLowerCase() === normalizedTarget) {
      return index + 1;
    }
  }

  throw new Error(`Section row "${firstCellValue}" not found`);
}

export function getCountryReferenceConfig(countryName: string) {
  return COUNTRY_REFERENCE_CONFIG[countryName as keyof typeof COUNTRY_REFERENCE_CONFIG] ?? null;
}

export function deriveCountryCodeFromMetroCity(
  metroCityName: string | null | undefined,
): string | null {
  if (!metroCityName) {
    return null;
  }

  for (const [countryName, config] of Object.entries(COUNTRY_REFERENCE_CONFIG)) {
    if (metroCityName === countryName) {
      return config.code;
    }

    const isGermanCity =
      countryName === "Germany" &&
      ["Bonn", "Frankfurt", "Hamburg", "Munich", "Stuttgart"].includes(metroCityName);
    const isPortugueseCity = countryName === "Portugal" && metroCityName === "Lisbon";
    const isUkCity = countryName === "United Kingdom" && metroCityName === "Birmingham";
    const isCostaRica = countryName === "Costa Rica" && metroCityName === "Costa Rica";

    if (isGermanCity || isPortugueseCity || isUkCity || isCostaRica) {
      return config.code;
    }
  }

  return null;
}

export function normalizeClientCode(masterClientName: string): string | null {
  return CLIENT_CODE_OVERRIDES[masterClientName as keyof typeof CLIENT_CODE_OVERRIDES] ?? null;
}

export function ensurePercentageValue(value: number | null): number | null {
  if (value === null) {
    return null;
  }

  return value <= 1 ? Math.round(value * 10000) / 100 : value;
}

export function deriveDisplayNameFromEnterpriseId(enterpriseId: string): string {
  return enterpriseId
    .split(".")
    .filter(Boolean)
    .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
    .join(" ");
}

export function deriveRoleTokens(...values: Array<string | null | undefined>): string[] {
  const tokenSet = new Set<string>();
  const combinedValue = values
    .map((value) => normalizeText(value))
    .filter((value): value is string => Boolean(value))
    .join(" ")
    .toUpperCase();

  if (
    combinedValue.includes("2D") ||
    combinedValue.includes("NUKE") ||
    combinedValue.includes("PHOTOSHOP") ||
    combinedValue.includes("RETOUCH") ||
    combinedValue.includes("ARTWORK") ||
    combinedValue.includes("MOTION DESIGN")
  ) {
    tokenSet.add("2D");
  }
  if (combinedValue.includes("3D") || combinedValue.includes("MODELING")) {
    tokenSet.add("3D");
  }
  if (
    combinedValue.includes("PROGRAM/DELIVERY MGMT") ||
    combinedValue.includes("PROJECT MANAGEMENT") ||
    combinedValue.includes("PRODUCER") ||
    combinedValue.includes("HEAD")
  ) {
    tokenSet.add("PM");
  }
  if (
    combinedValue.includes("ART DIRECTION") ||
    combinedValue.includes("ART DIRECTOR") ||
    combinedValue.includes("DIGITAL DESIGNER")
  ) {
    tokenSet.add("AD");
  }

  return Array.from(tokenSet);
}

export function deriveNormalizedChapter(
  rawChapter: string | null,
  roleTokens: string[],
): { chapter: string | null; chapterCode: string | null } {
  const firstRoleToken = roleTokens[0] ?? null;
  if (firstRoleToken) {
    const normalizedChapter = normalizeDispoChapterToken(firstRoleToken);
    if (normalizedChapter) {
      return {
        chapter: normalizedChapter,
        chapterCode: firstRoleToken,
      };
    }
  }

  return {
    chapter: rawChapter,
    chapterCode: null,
  };
}

export function mapChargeabilityResourceType(rawValue: string | null): {
  resourceType: ResourceType;
  warning: string | null;
} {
  if (!rawValue) {
    return {
      resourceType: ResourceType.EMPLOYEE,
      warning: null,
    };
  }

  const normalizedValue = rawValue.toLowerCase();
  if (normalizedValue.includes("freelancer")) {
    return { resourceType: ResourceType.FREELANCER, warning: null };
  }
  if (normalizedValue.includes("intern")) {
    return { resourceType: ResourceType.INTERN, warning: null };
  }
  if (normalizedValue.includes("student")) {
    return { resourceType: ResourceType.STUDENT, warning: null };
  }
  if (normalizedValue.includes("apprentice")) {
    return { resourceType: ResourceType.APPRENTICE, warning: null };
  }

  if (
    normalizedValue === "production studios" ||
    normalizedValue === "near&offshore" ||
    normalizedValue === "accenture" ||
    normalizedValue === "long-term absence"
  ) {
    return {
      resourceType: ResourceType.EMPLOYEE,
      warning: null,
    };
  }

  return {
    resourceType: ResourceType.EMPLOYEE,
    warning: `Unknown MV Ressource Type "${rawValue}" mapped to EMPLOYEE`,
  };
}

export function createAvailabilityFromFte(fte: number | null): Prisma.InputJsonValue {
  return createWeekdayAvailabilityFromFte(fte ?? 1) as unknown as Prisma.InputJsonValue;
}

export function buildBatchSummaryEntry(summary: Record<string, unknown>): Prisma.InputJsonValue {
  return summary as Prisma.InputJsonValue;
}

export async function ensureImportBatch(
  db: Pick<PrismaClient, "importBatch">,
  input: {
    chargeabilitySourceFile?: string;
    importBatchId?: string;
    notes?: string | null;
    planningSourceFile?: string;
    referenceSourceFile?: string;
  },
): Promise<{ id: string; summary: Record<string, unknown> }> {
  if (input.importBatchId) {
    const existing = await db.importBatch.findUnique({
      where: { id: input.importBatchId },
      select: { id: true, summary: true },
    });

    if (!existing) {
      throw new Error(`Import batch "${input.importBatchId}" not found`);
    }

    const updated = await db.importBatch.update({
      where: { id: input.importBatchId },
      data: {
        status: ImportBatchStatus.STAGING,
        ...(input.referenceSourceFile !== undefined
          ? { referenceSourceFile: input.referenceSourceFile }
          : {}),
        ...(input.chargeabilitySourceFile !== undefined
          ? { chargeabilitySourceFile: input.chargeabilitySourceFile }
          : {}),
        ...(input.planningSourceFile !== undefined
          ? { planningSourceFile: input.planningSourceFile }
          : {}),
        ...(input.notes !== undefined ? { notes: input.notes } : {}),
        startedAt: new Date(),
      },
      select: { id: true, summary: true },
    });

    return {
      id: updated.id,
      summary: toJsonObject(updated.summary),
    };
  }

  const created = await db.importBatch.create({
    data: {
      sourceSystem: "DISPO_V2",
      status: ImportBatchStatus.STAGING,
      ...(input.referenceSourceFile !== undefined
        ? { referenceSourceFile: input.referenceSourceFile }
        : {}),
      ...(input.chargeabilitySourceFile !== undefined
        ? { chargeabilitySourceFile: input.chargeabilitySourceFile }
        : {}),
      ...(input.planningSourceFile !== undefined
        ? { planningSourceFile: input.planningSourceFile }
        : {}),
      ...(input.notes !== undefined ? { notes: input.notes } : {}),
      startedAt: new Date(),
    },
    select: { id: true, summary: true },
  });

  return {
    id: created.id,
    summary: toJsonObject(created.summary),
  };
}

export async function finalizeImportBatchStage(
  db: Pick<PrismaClient, "importBatch">,
  input: {
    batchId: string;
    existingSummary: Record<string, unknown>;
    key: "chargeability" | "planning" | "projectResolution" | "reference" | "roster";
    summary: Record<string, unknown>;
  },
) {
  const nextSummary = {
    ...input.existingSummary,
    [input.key]: input.summary,
  };

  await db.importBatch.update({
    where: { id: input.batchId },
    data: {
      status: ImportBatchStatus.STAGED,
      stagedAt: new Date(),
      summary: buildBatchSummaryEntry(nextSummary),
    },
  });
}

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

  return {};
}

export function resolveCanonicalEnterpriseIdentity(value: string | null): string | null {
  return value ? normalizeCanonicalResourceIdentity(value) : null;
}

export function createSourceTrace(
  sourceKind: DispoImportSourceKind,
  sourceWorkbook: string,
  sourceSheet: string,
  sourceRow: number,
  sourceColumn?: string | null,
) {
  return {
    sourceKind,
    sourceWorkbook,
    sourceSheet,
    sourceRow,
    ...(sourceColumn !== undefined ? { sourceColumn } : {}),
  };
}

export const DISPO_REFERENCE_SHEET = "EID-Attr";
export const DISPO_PROJECT_REFERENCE_SHEET = "Project-Attr";
export const DISPO_CHARGEABILITY_SHEET = "ChgFC";
export const DISPO_PLANNING_SHEET = "Dispo";
export const DISPO_ROSTER_SHEET = "DispoRoster";
export const DISPO_ROSTER_SAP_SHEET = "SAP_data";

export { DispoImportSourceKind, DispoStagedRecordType, StagedRecordStatus };
