import { err, ok, type CommandContext } from "@tailor-platform/erp-kit/module";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PeriodCloseNotFoundError,
  InvalidStatusTransitionError,
  TrialBalanceImbalancedError,
} from "../lib/errors.generated";

export interface VerifyTrialBalanceInput {
  id: string;
}

/**
 * Function: verifyTrialBalance
 *
 * Checks that the period's trial balance is balanced (total debits equal total
 * credits across all accounts) and transitions the PeriodClose from
 * ADJUSTMENTS_POSTED to TRIAL_BALANCE_VERIFIED status.
 */
export async function run(db: Transaction, input: VerifyTrialBalanceInput, ctx: CommandContext) {
  const { id } = input;

  // 1. Find PeriodClose record
  const periodClose = await db
    .selectFrom("PeriodClose")
    .selectAll()
    .where("id", "=", id)
    .forUpdate()
    .executeTakeFirst();

  if (!periodClose) {
    return err(new PeriodCloseNotFoundError(id));
  }

  // 2. Validate status is ADJUSTMENTS_POSTED
  if (periodClose.status !== "ADJUSTMENTS_POSTED") {
    return err(new InvalidStatusTransitionError(id));
  }

  // 3. Retrieve the accounting period to find the company and period context
  const accountingPeriod = await db
    .selectFrom("AccountingPeriod")
    .selectAll()
    .where("id", "=", periodClose.accountingPeriodId)
    .executeTakeFirst();

  // 4. Retrieve the active chart of accounts for trial balance generation
  const chartOfAccounts = await db
    .selectFrom("ChartOfAccounts")
    .selectAll()
    .where("companyId", "=", accountingPeriod!.companyId)
    .where("status", "=", "ACTIVE")
    .executeTakeFirst();

  // 5. Generate or retrieve the trial balance for the period
  const trialBalance = await db
    .selectFrom("TrialBalance")
    .selectAll()
    .where("accountingPeriodId", "=", periodClose.accountingPeriodId)
    .where("chartOfAccountsId", "=", chartOfAccounts!.id)
    .executeTakeFirst();

  // 6. Verify trial balance is balanced
  if (!trialBalance || trialBalance.totalDebits !== trialBalance.totalCredits) {
    const discrepancy = trialBalance
      ? Math.abs(trialBalance.totalDebits - trialBalance.totalCredits)
      : 0;
    return err(new TrialBalanceImbalancedError(`${id} (discrepancy: ${discrepancy})`));
  }

  // 7. Transition PeriodClose to TRIAL_BALANCE_VERIFIED and update completionPercentage
  const now = new Date();
  const updated = await db
    .updateTable("PeriodClose")
    .set({
      status: "TRIAL_BALANCE_VERIFIED",
      trialBalanceId: trialBalance.id,
      completionPercentage: 62,
      updatedAt: now,
    })
    .where("id", "=", id)
    .returningAll()
    .executeTakeFirstOrThrow();

  // 8. Emit audit event
  const auditEvent = {
    type: "TRIAL_BALANCE_VERIFIED",
    from: "ADJUSTMENTS_POSTED",
    to: "TRIAL_BALANCE_VERIFIED",
    actorId: ctx.actorId,
    timestamp: now,
    trialBalanceId: trialBalance.id,
    isBalanced: true,
  };

  return ok({ periodClose: updated, auditEvent });
}
