import type { ReadonlyDB } from "@tailor-platform/erp-kit/core";
import type { Kysely, Selectable } from "@tailor-platform/sdk/kysely";
import type { Namespace as FLNamespace } from "../../finance-ledger/generated/kysely-tailordb";
import type { DB, Transaction } from "../generated/kysely-tailordb";

type QueryableDB = ReadonlyDB<DB> | Transaction;

type FLTables = FLNamespace["main-db"];
type CrossModuleTables = Pick<FLTables, "JournalEntry" | "JournalLine">;
type JournalLineRow = Selectable<FLTables["JournalLine"]>;

// Single boundary cast
function journalDb(db: QueryableDB) {
  return db as unknown as Kysely<CrossModuleTables>;
}

interface ActualAmountParams {
  accountingPeriodId: string;
  costObjectType: string;
  costObjectId: string;
}

interface SumActualAmountParams extends ActualAmountParams {
  costElementId: string;
}

function toNumber(value: number | null | undefined): number | null {
  return typeof value === "number" && Number.isFinite(value) ? value : null;
}

function getRowAmount(row: JournalLineRow): number {
  const debit = toNumber(row.functionalDebitAmount) ?? toNumber(row.debitAmount) ?? 0;
  const credit = toNumber(row.functionalCreditAmount) ?? toNumber(row.creditAmount) ?? 0;

  if (debit !== 0 || credit !== 0) {
    return debit !== 0 ? debit : credit;
  }

  return 0;
}

async function listActualAmountRows(db: QueryableDB, params: ActualAmountParams) {
  return journalDb(db)
    .selectFrom("JournalLine")
    .innerJoin("JournalEntry", "JournalEntry.id", "JournalLine.journalEntryId")
    .selectAll("JournalLine")
    .where("JournalEntry.accountingPeriodId", "=", params.accountingPeriodId)
    .where("JournalEntry.status", "=", "POSTED")
    .execute();
}

export async function listPostedActualAmountsByCostElement(
  db: QueryableDB,
  params: ActualAmountParams,
) {
  const rows = await listActualAmountRows(db, params);
  const totals = new Map<string, number>();

  for (const row of rows) {
    const costElementId = (row as Record<string, unknown>).costElementId;
    if (typeof costElementId !== "string") continue;
    totals.set(costElementId, (totals.get(costElementId) ?? 0) + getRowAmount(row));
  }

  return totals;
}

export async function sumPostedActualAmount(db: QueryableDB, params: SumActualAmountParams) {
  const rows = await listActualAmountRows(db, params);

  return rows.reduce((sum, row) => {
    const costElementId = (row as Record<string, unknown>).costElementId;
    if (costElementId !== params.costElementId) return sum;
    return sum + getRowAmount(row);
  }, 0);
}
