import type { Kysely } from "@tailor-platform/sdk/kysely";
import type { Namespace as FLNamespace } from "../../finance-ledger/generated/kysely-tailordb";
import type { Transaction } from "../generated/kysely-tailordb";

interface AllocationCycleLike {
  id: string;
  companyId: string;
  accountingPeriodId: string;
  allocationType: string;
}

interface AllocationResultLike {
  id: string;
  senderCostCenterId: string;
  receiverType: string;
  receiverId: string;
  allocatedAmount: number;
  costElementId: string;
}

// Single boundary cast — Kysely handles the rest
function journalEntryDb(db: Transaction) {
  return db as unknown as Kysely<Pick<FLNamespace["main-db"], "JournalEntry">>;
}

const JOURNAL_TYPE = "MISCELLANEOUS";

function buildPostingReferenceNumber(cycleId: string, resultId: string) {
  return `ALLOC-${cycleId}-${resultId}`;
}

function buildReversalReferenceNumber(referenceNumber: string) {
  return `REV-${referenceNumber}`;
}

function buildPostingSourceDocumentReference(cycleId: string) {
  return `AllocationCycle:${cycleId}`;
}

function buildReversalSourceDocumentReference(cycleId: string) {
  return `AllocationCycleReversal:${cycleId}`;
}

function buildPostingDescription(
  cycle: AllocationCycleLike,
  result: AllocationResultLike,
  costElementType: string | null,
) {
  const elementRole = cycle.allocationType === "ASSESSMENT" ? "secondary" : "primary";

  return [
    `${cycle.allocationType} allocation posting`,
    `sender=${result.senderCostCenterId}`,
    `receiver=${result.receiverType}:${result.receiverId}`,
    `costElement=${result.costElementId}`,
    `role=${costElementType ?? elementRole}`,
    `amount=${result.allocatedAmount}`,
  ].join(" | ");
}

export async function createAllocationPostingEntries(
  db: Transaction,
  cycle: AllocationCycleLike,
  allocationResults: AllocationResultLike[],
) {
  const now = new Date();
  const jDb = journalEntryDb(db);
  const createdEntries = [];

  for (const result of allocationResults) {
    const costElement = await db
      .selectFrom("CostElement")
      .selectAll()
      .where("id", "=", result.costElementId)
      .executeTakeFirst();

    const costElementType = costElement?.type ?? null;

    const journalEntry = await jDb
      .insertInto("JournalEntry")
      .values({
        companyId: cycle.companyId,
        accountingPeriodId: cycle.accountingPeriodId,
        entryDate: now,
        journalType: JOURNAL_TYPE,
        referenceNumber: buildPostingReferenceNumber(cycle.id, result.id),
        status: "POSTED",
        description: buildPostingDescription(cycle, result, costElementType),
        sourceDocumentReference: buildPostingSourceDocumentReference(cycle.id),
        adjustingEntryType: null,
        reversalOfId: null,
        postedAt: now,
        createdAt: now,
        updatedAt: null,
      })
      .returningAll()
      .executeTakeFirst();

    createdEntries.push(journalEntry);
  }

  return createdEntries;
}

export async function createAllocationReversalEntries(db: Transaction, cycle: AllocationCycleLike) {
  const jDb = journalEntryDb(db);

  const originalEntries = await jDb
    .selectFrom("JournalEntry")
    .selectAll()
    .where("sourceDocumentReference", "=", buildPostingSourceDocumentReference(cycle.id))
    .where("status", "=", "POSTED")
    .execute();

  const now = new Date();
  const reversalEntries = [];

  for (const entry of originalEntries) {
    const reversalEntry = await jDb
      .insertInto("JournalEntry")
      .values({
        companyId: cycle.companyId,
        accountingPeriodId: cycle.accountingPeriodId,
        entryDate: now,
        journalType: JOURNAL_TYPE,
        referenceNumber: buildReversalReferenceNumber(entry.referenceNumber),
        status: "POSTED",
        description: `Reversal of ${entry.referenceNumber}`,
        sourceDocumentReference: buildReversalSourceDocumentReference(cycle.id),
        adjustingEntryType: null,
        reversalOfId: entry.id,
        postedAt: now,
        createdAt: now,
        updatedAt: null,
      })
      .returningAll()
      .executeTakeFirst();

    await jDb
      .updateTable("JournalEntry")
      .set({ status: "REVERSED" as const, updatedAt: now })
      .where("id", "=", entry.id)
      .executeTakeFirst();

    reversalEntries.push(reversalEntry);
  }

  return reversalEntries;
}
