import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PeriodCloseNotFoundError,
  InvalidStatusTransitionError,
  TrialBalanceImbalancedError,
} from "../lib/errors.generated";
import {
  adjustmentsPostedPeriodClose,
  subledgersVerifiedPeriodClose,
  trialBalanceVerifiedPeriodClose,
  baseTrialBalance,
  baseAccountingPeriod,
  baseChartOfAccounts,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./verifyTrialBalance";

describe("verifyTrialBalance", () => {
  it("returns error when PeriodClose record does not exist", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(undefined); // PeriodClose lookup

    const result = await run(db, { id: "nonexistent" }, commandCtx);

    expectErr(result, PeriodCloseNotFoundError);
  });

  it("returns error when PeriodClose is in SUBLEDGERS_VERIFIED status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(subledgersVerifiedPeriodClose); // PeriodClose lookup

    const result = await run(db, { id: subledgersVerifiedPeriodClose.id }, commandCtx);

    expectErr(result, InvalidStatusTransitionError);
  });

  it("returns error when PeriodClose is already in TRIAL_BALANCE_VERIFIED status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(trialBalanceVerifiedPeriodClose); // PeriodClose lookup

    const result = await run(db, { id: trialBalanceVerifiedPeriodClose.id }, commandCtx);

    expectErr(result, InvalidStatusTransitionError);
  });

  it("returns error when trial balance has a non-zero debit-credit discrepancy", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const imbalancedTrialBalance = {
      ...baseTrialBalance,
      id: "trial-balance-imbalanced",
      totalDebits: 10000,
      totalCredits: 9500,
      isBalanced: false,
    };
    spies.select
      .mockReturnValueOnce(adjustmentsPostedPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
      .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
      .mockReturnValueOnce(imbalancedTrialBalance); // TrialBalance lookup

    const result = await run(db, { id: adjustmentsPostedPeriodClose.id }, commandCtx);

    expectErr(result, TrialBalanceImbalancedError);
  });

  it("transitions ADJUSTMENTS_POSTED to TRIAL_BALANCE_VERIFIED when trial balance is balanced", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedPeriodClose = {
      ...adjustmentsPostedPeriodClose,
      status: "TRIAL_BALANCE_VERIFIED",
      trialBalanceId: baseTrialBalance.id,
      completionPercentage: 62,
    };
    spies.select
      .mockReturnValueOnce(adjustmentsPostedPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
      .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
      .mockReturnValueOnce(baseTrialBalance); // TrialBalance lookup (balanced)
    spies.update.mockReturnValueOnce(updatedPeriodClose);

    const result = await run(db, { id: adjustmentsPostedPeriodClose.id }, commandCtx);

    const value = expectOk(result);
    expect(value.periodClose.status).toBe("TRIAL_BALANCE_VERIFIED");
  });

  it("updates completionPercentage after successful verification", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedPeriodClose = {
      ...adjustmentsPostedPeriodClose,
      status: "TRIAL_BALANCE_VERIFIED",
      trialBalanceId: baseTrialBalance.id,
      completionPercentage: 62,
    };
    spies.select
      .mockReturnValueOnce(adjustmentsPostedPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
      .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
      .mockReturnValueOnce(baseTrialBalance); // TrialBalance lookup (balanced)
    spies.update.mockReturnValueOnce(updatedPeriodClose);

    const result = await run(db, { id: adjustmentsPostedPeriodClose.id }, commandCtx);

    const value = expectOk(result);
    expect(value.periodClose.completionPercentage).toBe(62);
  });

  it("emits audit event recording trial balance result and status transition", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedPeriodClose = {
      ...adjustmentsPostedPeriodClose,
      status: "TRIAL_BALANCE_VERIFIED",
      trialBalanceId: baseTrialBalance.id,
      completionPercentage: 62,
    };
    spies.select
      .mockReturnValueOnce(adjustmentsPostedPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
      .mockReturnValueOnce(baseChartOfAccounts) // ChartOfAccounts lookup
      .mockReturnValueOnce(baseTrialBalance); // TrialBalance lookup (balanced)
    spies.update.mockReturnValueOnce(updatedPeriodClose);

    const result = await run(db, { id: adjustmentsPostedPeriodClose.id }, commandCtx);

    const value = expectOk(result);
    expect(value.auditEvent).toBeDefined();
    expect(value.auditEvent.actorId).toBe(commandCtx.actorId);
    expect(value.auditEvent.timestamp).toBeInstanceOf(Date);
    expect(value.auditEvent.from).toBe("ADJUSTMENTS_POSTED");
    expect(value.auditEvent.to).toBe("TRIAL_BALANCE_VERIFIED");
    expect(value.auditEvent.trialBalanceId).toBe(baseTrialBalance.id);
    expect(value.auditEvent.isBalanced).toBe(true);
  });
});
