import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  PeriodCloseNotFoundError,
  InvalidStatusTransitionError,
  PendingSubledgerTransactionsError,
} from "../lib/errors.generated";
import {
  basePeriodClose,
  inProgressPeriodClose,
  subledgersVerifiedPeriodClose,
  baseJournalEntry,
  baseAccountingPeriod,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./verifySubledgerTransfers";

describe("verifySubledgerTransfers", () => {
  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 NOT_STARTED status", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select.mockReturnValueOnce(basePeriodClose); // PeriodClose in NOT_STARTED

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

    expectErr(result, InvalidStatusTransitionError);
  });

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

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

    expectErr(result, InvalidStatusTransitionError);
  });

  it("returns error when purchase module has pending unposted transactions", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const purchaseDraft = {
      ...baseJournalEntry,
      id: "je-purchase-draft",
      journalType: "PURCHASE",
      accountingPeriodId: baseAccountingPeriod.id,
      status: "DRAFT",
    };
    spies.select
      .mockReturnValueOnce(inProgressPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce([purchaseDraft]) // purchase DRAFT entries
      .mockReturnValueOnce([]) // sales: no pending
      .mockReturnValueOnce([]) // inventory: no pending
      .mockReturnValueOnce([]); // manufacturing: no pending

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

    expectErr(result, PendingSubledgerTransactionsError);
  });

  it("returns error when sales module has pending unposted transactions", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const salesDraft = {
      ...baseJournalEntry,
      id: "je-sales-draft",
      journalType: "SALES",
      accountingPeriodId: baseAccountingPeriod.id,
      status: "DRAFT",
    };
    spies.select
      .mockReturnValueOnce(inProgressPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce([]) // purchase: no pending
      .mockReturnValueOnce([salesDraft]) // sales: pending
      .mockReturnValueOnce([]) // inventory: no pending
      .mockReturnValueOnce([]); // manufacturing: no pending

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

    expectErr(result, PendingSubledgerTransactionsError);
  });

  it("returns error when inventory module has pending unposted transactions", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const inventoryDraft = {
      ...baseJournalEntry,
      id: "je-inventory-draft",
      journalType: "MISCELLANEOUS",
      accountingPeriodId: baseAccountingPeriod.id,
      status: "DRAFT",
    };
    spies.select
      .mockReturnValueOnce(inProgressPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce([]) // purchase: no pending
      .mockReturnValueOnce([]) // sales: no pending
      .mockReturnValueOnce([inventoryDraft]) // inventory: pending
      .mockReturnValueOnce([]); // manufacturing: no pending

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

    expectErr(result, PendingSubledgerTransactionsError);
  });

  it("returns error when manufacturing module has pending unposted transactions", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const manufacturingDraft = {
      ...baseJournalEntry,
      id: "je-manufacturing-draft",
      journalType: "MISCELLANEOUS",
      accountingPeriodId: baseAccountingPeriod.id,
      status: "DRAFT",
    };
    spies.select
      .mockReturnValueOnce(inProgressPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce([]) // purchase: no pending
      .mockReturnValueOnce([]) // sales: no pending
      .mockReturnValueOnce([]) // inventory: no pending
      .mockReturnValueOnce([manufacturingDraft]); // manufacturing: pending

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

    expectErr(result, PendingSubledgerTransactionsError);
  });

  it("transitions IN_PROGRESS to SUBLEDGERS_VERIFIED when all subledgers are reconciled", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedPeriodClose = {
      ...inProgressPeriodClose,
      status: "SUBLEDGERS_VERIFIED",
      completionPercentage: 25,
    };
    spies.select
      .mockReturnValueOnce(inProgressPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce([]) // purchase: no pending
      .mockReturnValueOnce([]) // sales: no pending
      .mockReturnValueOnce([]) // inventory: no pending
      .mockReturnValueOnce([]); // manufacturing: no pending
    spies.update.mockReturnValueOnce(updatedPeriodClose);

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

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

  it("updates completionPercentage after successful verification", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedPeriodClose = {
      ...inProgressPeriodClose,
      status: "SUBLEDGERS_VERIFIED",
      completionPercentage: 25,
    };
    spies.select
      .mockReturnValueOnce(inProgressPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce([]) // purchase: no pending
      .mockReturnValueOnce([]) // sales: no pending
      .mockReturnValueOnce([]) // inventory: no pending
      .mockReturnValueOnce([]); // manufacturing: no pending
    spies.update.mockReturnValueOnce(updatedPeriodClose);

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

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

  it("emits audit event recording subledger verification results and status transition", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const updatedPeriodClose = {
      ...inProgressPeriodClose,
      status: "SUBLEDGERS_VERIFIED",
      completionPercentage: 25,
    };
    spies.select
      .mockReturnValueOnce(inProgressPeriodClose) // PeriodClose lookup
      .mockReturnValueOnce([]) // purchase: no pending
      .mockReturnValueOnce([]) // sales: no pending
      .mockReturnValueOnce([]) // inventory: no pending
      .mockReturnValueOnce([]); // manufacturing: no pending
    spies.update.mockReturnValueOnce(updatedPeriodClose);

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

    const value = expectOk(result);
    expect(value.auditEvent).toBeDefined();
    expect(value.auditEvent.type).toBe("SUBLEDGER_VERIFICATION_COMPLETED");
    expect(value.auditEvent.actorId).toBe(commandCtx.actorId);
    expect(value.auditEvent.from).toBe("IN_PROGRESS");
    expect(value.auditEvent.to).toBe("SUBLEDGERS_VERIFIED");
    expect(value.auditEvent.verificationResults).toHaveLength(4);
    expect(
      value.auditEvent.verificationResults.every((r: { reconciled: boolean }) => r.reconciled),
    ).toBe(true);
  });
});
