import { describe, expect, it } from "vitest";
import { createMockDb } from "../../../testing/index";
import type { Transaction } from "../generated/kysely-tailordb";
import {
  AccountingPeriodNotFoundError,
  InvalidPeriodStatusError,
  CloseAlreadyInProgressError,
  ConcurrentCloseConflictError,
} from "../lib/errors.generated";
import {
  baseAccountingPeriod,
  neverOpenedPeriod,
  closedPeriod,
  permanentlyClosedPeriod,
  basePeriodClose,
} from "../testing/fixtures";
import { commandCtx, expectErr, expectOk } from "../testing/commandTestUtils";
import { run } from "./initiatePeriodClose";

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

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

    expectErr(result, AccountingPeriodNotFoundError);
  });

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

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

    expectErr(result, InvalidPeriodStatusError);
  });

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

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

    expectErr(result, InvalidPeriodStatusError);
  });

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

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

    expectErr(result, InvalidPeriodStatusError);
  });

  it("returns error when period already has an active close in progress", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseAccountingPeriod) // period lookup
      .mockReturnValueOnce(basePeriodClose); // existing active PeriodClose

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

    expectErr(result, CloseAlreadyInProgressError);
  });

  it("returns error on concurrent close attempt for the same period", async () => {
    const { db, spies } = createMockDb<Transaction>();
    spies.select
      .mockReturnValueOnce(baseAccountingPeriod) // period lookup
      .mockReturnValueOnce(undefined); // no existing PeriodClose
    spies.insert.mockImplementationOnce(() => {
      throw new Error("unique constraint violation");
    });

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

    expectErr(result, ConcurrentCloseConflictError);
  });

  it("creates PeriodClose in NOT_STARTED status for an OPEN period", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdPeriodClose = {
      ...basePeriodClose,
      initiatedByUserId: commandCtx.actorId,
    };
    spies.select
      .mockReturnValueOnce(baseAccountingPeriod) // period lookup
      .mockReturnValueOnce(undefined); // no existing PeriodClose
    spies.insert.mockReturnValueOnce(createdPeriodClose);

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

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

  it("records the initiating user's userId", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdPeriodClose = {
      ...basePeriodClose,
      initiatedByUserId: commandCtx.actorId,
    };
    spies.select
      .mockReturnValueOnce(baseAccountingPeriod) // period lookup
      .mockReturnValueOnce(undefined); // no existing PeriodClose
    spies.insert.mockReturnValueOnce(createdPeriodClose);

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

    const value = expectOk(result);
    expect(value.periodClose.initiatedByUserId).toBe(commandCtx.actorId);
  });

  it("sets completionPercentage to 0", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdPeriodClose = {
      ...basePeriodClose,
      initiatedByUserId: commandCtx.actorId,
      completionPercentage: 0,
    };
    spies.select
      .mockReturnValueOnce(baseAccountingPeriod) // period lookup
      .mockReturnValueOnce(undefined); // no existing PeriodClose
    spies.insert.mockReturnValueOnce(createdPeriodClose);

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

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

  it("emits audit event recording acting user, timestamp, and period reference", async () => {
    const { db, spies } = createMockDb<Transaction>();
    const createdPeriodClose = {
      ...basePeriodClose,
      initiatedByUserId: commandCtx.actorId,
    };
    spies.select
      .mockReturnValueOnce(baseAccountingPeriod) // period lookup
      .mockReturnValueOnce(undefined); // no existing PeriodClose
    spies.insert.mockReturnValueOnce(createdPeriodClose);

    const result = await run(db, { accountingPeriodId: baseAccountingPeriod.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.accountingPeriodId).toBe(baseAccountingPeriod.id);
  });
});
