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

describe("generatePreliminaryStatements", () => {
  const tbVerifiedWithTrialBalance = {
    ...trialBalanceVerifiedPeriodClose,
    trialBalanceId: baseTrialBalance.id,
  };

  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 SOFT_LOCKED status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(softLockedPeriodClose); // PeriodClose lookup

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

    expectErr(result, InvalidStatusTransitionError);
  });

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

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

    expectErr(result, InvalidStatusTransitionError);
  });

  it("returns error when trial balance is not found", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(tbVerifiedWithTrialBalance) // PeriodClose lookup
      .mockReturnValueOnce(undefined); // TrialBalance lookup

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

    expectErr(result, TrialBalanceNotFoundError);
  });

  it("generates both BS and P&L and transitions to UNDER_REVIEW with completionPercentage=75", async () => {
    const { db, spies } = createMockDb<Transaction>();

    const bsStatement = {
      ...baseFinancialStatement,
      id: "fs-bs-1",
      statementType: "BALANCE_SHEET",
      status: "DRAFT",
    };
    const plStatement = {
      ...baseFinancialStatement,
      id: "fs-pl-1",
      statementType: "PROFIT_AND_LOSS",
      status: "DRAFT",
    };
    const updatedPeriodClose = {
      ...tbVerifiedWithTrialBalance,
      status: "UNDER_REVIEW",
      completionPercentage: 75,
      preliminaryStatementIds: [bsStatement.id, plStatement.id],
    };

    spies.select
      .mockReturnValueOnce(tbVerifiedWithTrialBalance) // PeriodClose lookup
      .mockReturnValueOnce(baseTrialBalance) // TrialBalance lookup
      .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
      .mockReturnValueOnce(baseChartOfAccounts); // ChartOfAccounts lookup
    spies.insert
      .mockReturnValueOnce(bsStatement) // BalanceSheet insert
      .mockReturnValueOnce(plStatement); // P&L insert
    spies.update.mockReturnValueOnce(updatedPeriodClose);

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

    const value = expectOk(result);
    expect(value.periodClose.status).toBe("UNDER_REVIEW");
    expect(value.periodClose.completionPercentage).toBe(75);
    expect(value.statements).toHaveLength(2);
    expect(value.statements[0].statementType).toBe("BALANCE_SHEET");
    expect(value.statements[1].statementType).toBe("PROFIT_AND_LOSS");
    expect(value.statements[0].status).toBe("DRAFT");
    expect(value.statements[1].status).toBe("DRAFT");
    expect(value.periodClose.preliminaryStatementIds).toEqual([bsStatement.id, plStatement.id]);
  });

  it("emits audit event recording status transition and statement ids", async () => {
    const { db, spies } = createMockDb<Transaction>();

    const bsStatement = {
      ...baseFinancialStatement,
      id: "fs-bs-2",
      statementType: "BALANCE_SHEET",
      status: "DRAFT",
    };
    const plStatement = {
      ...baseFinancialStatement,
      id: "fs-pl-2",
      statementType: "PROFIT_AND_LOSS",
      status: "DRAFT",
    };
    const updatedPeriodClose = {
      ...tbVerifiedWithTrialBalance,
      status: "UNDER_REVIEW",
      completionPercentage: 75,
      preliminaryStatementIds: [bsStatement.id, plStatement.id],
    };

    spies.select
      .mockReturnValueOnce(tbVerifiedWithTrialBalance) // PeriodClose lookup
      .mockReturnValueOnce(baseTrialBalance) // TrialBalance lookup
      .mockReturnValueOnce(baseAccountingPeriod) // AccountingPeriod lookup
      .mockReturnValueOnce(baseChartOfAccounts); // ChartOfAccounts lookup
    spies.insert.mockReturnValueOnce(bsStatement).mockReturnValueOnce(plStatement);
    spies.update.mockReturnValueOnce(updatedPeriodClose);

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

    const value = expectOk(result);
    expect(value.auditTrail).toBeDefined();
    expect(value.auditTrail).toHaveLength(1);
    expect(value.auditTrail[0].type).toBe("STATUS_TRANSITION");
    expect(value.auditTrail[0].from).toBe("TRIAL_BALANCE_VERIFIED");
    expect(value.auditTrail[0].to).toBe("UNDER_REVIEW");
    expect(value.auditTrail[0].actorId).toBe(commandCtx.actorId);
    expect(value.auditTrail[0].timestamp).toBeInstanceOf(Date);
    expect(value.auditTrail[0].statementIds).toEqual([bsStatement.id, plStatement.id]);
  });
});
