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;
}

interface JournalEntryLike {
  id: string;
  referenceNumber: string;
  sourceDocumentReference: string | null;
}

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 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 db
      .insertInto("JournalEntry" as never)
      .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,
      } as never)
      .returningAll()
      .executeTakeFirst();

    createdEntries.push(journalEntry);
  }

  return createdEntries;
}

export async function createAllocationReversalEntries(db: Transaction, cycle: AllocationCycleLike) {
  const originalEntries = (await db
    .selectFrom("JournalEntry" as never)
    .selectAll()
    .where(
      "sourceDocumentReference" as never,
      "=",
      buildPostingSourceDocumentReference(cycle.id) as never,
    )
    .where("status" as never, "=", "POSTED" as never)
    .execute()) as JournalEntryLike[];

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

  for (const entry of originalEntries) {
    const reversalEntry = await db
      .insertInto("JournalEntry" as never)
      .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,
      } as never)
      .returningAll()
      .executeTakeFirst();

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

    reversalEntries.push(reversalEntry);
  }

  return reversalEntries;
}
