import type { DB, Transaction } from "../generated/kysely-tailordb";
import type { ReadonlyDB } from "@tailor-platform/erp-kit/module";

type QueryableDB = ReadonlyDB<DB> | Transaction;

type ActualAmountRow = Record<string, unknown>;

interface CrossModuleJournalLineQuery {
  innerJoin(table: string, left: string, right: string): CrossModuleJournalLineQuery;
  selectAll(table: string): CrossModuleJournalLineQuery;
  where(column: string, operator: string, value: string): CrossModuleJournalLineQuery;
  execute(): Promise<unknown>;
}

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

interface SumActualAmountParams extends ActualAmountParams {
  costElementId: string;
}

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

function getRowAmount(row: ActualAmountRow): number {
  const explicitAmount =
    toNumber(row.amount) ??
    toNumber(row.functionalAmount) ??
    toNumber(row.actualAmount) ??
    toNumber(row.netAmount);
  if (explicitAmount !== null) {
    return explicitAmount;
  }

  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) {
  const query = db.selectFrom("JournalLine" as never) as unknown as CrossModuleJournalLineQuery;
  const rows = await query
    .innerJoin("JournalEntry", "JournalEntry.id", "JournalLine.journalEntryId")
    .selectAll("JournalLine")
    .where("JournalEntry.accountingPeriodId", "=", params.accountingPeriodId)
    .where("JournalEntry.status", "=", "POSTED")
    .where("JournalLine.costObjectType", "=", params.costObjectType)
    .where("JournalLine.costObjectId", "=", params.costObjectId)
    .execute();

  return rows as ActualAmountRow[];
}

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 = typeof row.costElementId === "string" ? row.costElementId : null;
    if (!costElementId) {
      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 = typeof row.costElementId === "string" ? row.costElementId : null;
    if (costElementId !== params.costElementId) {
      return sum;
    }

    return sum + getRowAmount(row);
  }, 0);
}
