import type { Prisma } from "@capakraken/db";
import { DispoImportSourceKind, StagedRecordStatus } from "@capakraken/db";
import { parseMandatoryDispoReferenceWorkbook } from "./parse-reference-workbook.js";
import { ensureImportBatch, finalizeImportBatchStage, getWorkbookFileName, type DispoImportDbClient, type DispoReferenceImportInput, toJsonObject } from "./shared.js";

async function upsertRootClient(
  db: DispoImportDbClient,
  input: { clientCode: string | null; name: string; sortOrder: number },
) {
  const existing = await db.client.findFirst({
    where: { name: input.name, parentId: null },
    select: { id: true },
  });

  if (existing) {
    return db.client.update({
      where: { id: existing.id },
      data: {
        ...(input.clientCode ? { code: input.clientCode } : {}),
        sortOrder: input.sortOrder,
        isActive: true,
      },
      select: { id: true },
    });
  }

  return db.client.create({
    data: {
      name: input.name,
      ...(input.clientCode ? { code: input.clientCode } : {}),
      sortOrder: input.sortOrder,
      isActive: true,
    },
    select: { id: true },
  });
}

async function upsertRootOrgUnit(
  db: DispoImportDbClient,
  input: { name: string; level: number; sortOrder: number },
) {
  const existing = await db.orgUnit.findFirst({
    where: { name: input.name, level: input.level, parentId: null },
    select: { id: true },
  });

  if (existing) {
    return db.orgUnit.update({
      where: { id: existing.id },
      data: { sortOrder: input.sortOrder, isActive: true },
      select: { id: true },
    });
  }

  return db.orgUnit.create({
    data: {
      name: input.name,
      level: input.level,
      sortOrder: input.sortOrder,
      isActive: true,
    },
    select: { id: true },
  });
}

export interface StageDispoReferenceDataResult {
  batchId: string;
  counts: {
    countries: number;
    managementLevelGroups: number;
    managementLevels: number;
    metroCities: number;
    orgUnits: number;
    stagedClients: number;
    unresolved: number;
    warnings: number;
  };
}

export async function stageDispoReferenceData(
  db: DispoImportDbClient,
  input: DispoReferenceImportInput,
): Promise<StageDispoReferenceDataResult> {
  const batchInput: {
    importBatchId?: string;
    notes?: string | null;
    referenceSourceFile: string;
  } = {
    referenceSourceFile: getWorkbookFileName(input.referenceWorkbookPath),
  };

  if (input.importBatchId !== undefined) {
    batchInput.importBatchId = input.importBatchId;
  }
  if (input.notes !== undefined) {
    batchInput.notes = input.notes;
  }

  const batch = await ensureImportBatch(db, batchInput);
  const parsed = await parseMandatoryDispoReferenceWorkbook(input.referenceWorkbookPath);

  for (const country of parsed.countries) {
    const createdCountry = await db.country.upsert({
      where: { code: country.countryCode },
      update: {
        name: country.name,
        dailyWorkingHours: country.dailyWorkingHours,
        ...(country.scheduleRules !== undefined ? { scheduleRules: country.scheduleRules } : {}),
        isActive: true,
      },
      create: {
        code: country.countryCode,
        name: country.name,
        dailyWorkingHours: country.dailyWorkingHours,
        ...(country.scheduleRules !== undefined ? { scheduleRules: country.scheduleRules } : {}),
        isActive: true,
      },
    });

    for (const metroCityName of country.metroCities) {
      await db.metroCity.upsert({
        where: {
          countryId_name: {
            countryId: createdCountry.id,
            name: metroCityName,
          },
        },
        update: {},
        create: {
          countryId: createdCountry.id,
          name: metroCityName,
        },
      });
    }
  }

  const rootOrgUnits = parsed.orgUnits.filter((orgUnit) => orgUnit.parentName === null);
  const rootOrgUnitIdByName = new Map<string, string>();
  for (const rootOrgUnit of rootOrgUnits) {
    const created = await upsertRootOrgUnit(db, {
      name: rootOrgUnit.name,
      level: rootOrgUnit.level,
      sortOrder: rootOrgUnit.sortOrder,
    });
    rootOrgUnitIdByName.set(rootOrgUnit.name, created.id);
  }

  const orgUnitIdByKey = new Map<string, string>();
  for (const rootOrgUnit of rootOrgUnits) {
    const rootId = rootOrgUnitIdByName.get(rootOrgUnit.name);
    if (rootId) {
      orgUnitIdByKey.set(`${rootOrgUnit.level}:${rootOrgUnit.name}`, rootId);
    }
  }

  for (const orgUnit of parsed.orgUnits.filter((entry) => entry.parentName !== null)) {
    const parentLevel = orgUnit.level - 1;
    const parentId = orgUnitIdByKey.get(`${parentLevel}:${orgUnit.parentName}`);
    if (!parentId) {
      continue;
    }

    const created = await db.orgUnit.upsert({
      where: {
        parentId_name: {
          parentId,
          name: orgUnit.name,
        },
      },
      update: {
        sortOrder: orgUnit.sortOrder,
        isActive: true,
      },
      create: {
        parentId,
        name: orgUnit.name,
        level: orgUnit.level,
        sortOrder: orgUnit.sortOrder,
        isActive: true,
      },
      select: { id: true },
    });
    orgUnitIdByKey.set(`${orgUnit.level}:${orgUnit.name}`, created.id);
  }

  let managementLevelCount = 0;
  for (const group of parsed.managementLevelGroups) {
    const createdGroup = await db.managementLevelGroup.upsert({
      where: { name: group.name },
      update: {
        targetPercentage: group.targetPercentage,
        sortOrder: group.sortOrder,
      },
      create: {
        name: group.name,
        targetPercentage: group.targetPercentage,
        sortOrder: group.sortOrder,
      },
    });

    for (const levelName of group.levels) {
      managementLevelCount += 1;
      await db.managementLevel.upsert({
        where: { name: levelName },
        update: { groupId: createdGroup.id },
        create: { name: levelName, groupId: createdGroup.id },
      });
    }
  }

  const rootClientIdByName = new Map<string, string>();
  for (const rootClient of parsed.clients.filter((entry) => entry.parentName === null)) {
    const created = await upsertRootClient(db, {
      clientCode: rootClient.clientCode,
      name: rootClient.name,
      sortOrder: rootClient.sortOrder,
    });
    rootClientIdByName.set(rootClient.name, created.id);
  }

  for (const client of parsed.clients.filter((entry) => entry.parentName !== null)) {
    const parentId = rootClientIdByName.get(client.parentName ?? "");
    if (!parentId) {
      continue;
    }

    const existing = await db.client.findFirst({
      where: { name: client.name, parentId },
      select: { id: true },
    });

    if (existing) {
      await db.client.update({
        where: { id: existing.id },
        data: {
          sortOrder: client.sortOrder,
          isActive: true,
        },
      });
    } else {
      await db.client.create({
        data: {
          name: client.name,
          parentId,
          sortOrder: client.sortOrder,
          isActive: true,
        },
      });
    }
  }

  await db.stagedClient.deleteMany({
    where: {
      importBatchId: batch.id,
      sourceKind: DispoImportSourceKind.REFERENCE,
    },
  });

  if (parsed.clients.length > 0) {
    await db.stagedClient.createMany({
      data: parsed.clients.map((client) => ({
        importBatchId: batch.id,
        status: StagedRecordStatus.NORMALIZED,
        sourceKind: DispoImportSourceKind.REFERENCE,
        sourceWorkbook: getWorkbookFileName(input.referenceWorkbookPath),
        sourceSheet: "Project-Attr",
        sourceRow: client.sourceRow,
        sourceColumn: client.sourceColumn,
        clientCode: client.clientCode,
        parentClientCode: client.parentClientCode,
        name: client.name,
        sortOrder: client.sortOrder,
        isActive: true,
        warnings: [],
        rawPayload: {
          name: client.name,
          parentName: client.parentName,
        } as Prisma.InputJsonValue,
        normalizedData: {
          clientCode: client.clientCode,
          name: client.name,
          parentClientCode: client.parentClientCode,
          parentName: client.parentName,
        } as Prisma.InputJsonValue,
      })),
    });
  }

  await db.stagedUnresolvedRecord.deleteMany({
    where: {
      importBatchId: batch.id,
      sourceKind: DispoImportSourceKind.REFERENCE,
    },
  });

  const summary = {
    countries: parsed.countries.length,
    managementLevelGroups: parsed.managementLevelGroups.length,
    managementLevels: managementLevelCount,
    metroCities: parsed.countries.reduce((count, country) => count + country.metroCities.length, 0),
    orgUnits: parsed.orgUnits.length,
    stagedClients: parsed.clients.length,
    unresolved: 0,
    warnings: parsed.warnings.length,
  };

  await finalizeImportBatchStage(db, {
    batchId: batch.id,
    existingSummary: batch.summary,
    key: "reference",
    summary,
  });

  return {
    batchId: batch.id,
    counts: summary,
  };
}
