import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import { RequestNotFoundError } from "../lib/errors.generated";
import { approveDecision, pendingRequest } from "../testing/fixtures";
import { run } from "./getApprovalDecisionHistory";

describe("getApprovalDecisionHistory", () => {
  it("returns decisions ordered by decidedAt ASC", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([pendingRequest], [approveDecision]);

    const result = await run(mock.db, { approvalRequestId: pendingRequest.id });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.approvalDecisions).toEqual([approveDecision]);
    }
    expect(mock.selects[1].sql).toMatch(/order by .*"decidedAt" asc/i);
  });

  it("returns mixed decision rows (APPROVE / REJECT / SEND_BACK / DELEGATE / WITHDRAW / CANCEL / RESUBMIT) verbatim", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const rejectDecision = {
      ...approveDecision,
      id: "decision-reject-1",
      decision: "REJECT" as const,
      comment: "Exceeds Q2 budget cap",
      decidedAt: new Date("2024-03-02T01:00:00.000Z"),
    };
    const sendBackDecision = {
      ...approveDecision,
      id: "decision-sendback-1",
      decision: "SEND_BACK" as const,
      comment: "Attach the signed vendor quote",
      decidedAt: new Date("2024-03-02T01:30:00.000Z"),
    };
    const delegateDecision = {
      ...approveDecision,
      id: "decision-delegate-1",
      decision: "DELEGATE" as const,
      delegatedToUserId: "user-director-1",
      decidedAt: new Date("2024-03-02T02:00:00.000Z"),
    };
    const withdrawDecision = {
      ...approveDecision,
      id: "decision-withdraw-1",
      approvalStepId: null,
      decision: "WITHDRAW" as const,
      decidedAt: new Date("2024-03-02T03:00:00.000Z"),
    };
    const cancelDecision = {
      ...approveDecision,
      id: "decision-cancel-1",
      approvalStepId: null,
      decision: "CANCEL" as const,
      comment: "Superseded by new request",
      decidedAt: new Date("2024-03-02T04:00:00.000Z"),
    };
    const resubmitDecision = {
      ...approveDecision,
      id: "decision-resubmit-1",
      approvalStepId: null,
      decision: "RESUBMIT" as const,
      decidedAt: new Date("2024-03-02T05:00:00.000Z"),
    };
    const mixed = [
      approveDecision,
      rejectDecision,
      sendBackDecision,
      delegateDecision,
      withdrawDecision,
      cancelDecision,
      resubmitDecision,
    ];

    mock.enqueueResults([pendingRequest], mixed);

    const result = await run(mock.db, { approvalRequestId: pendingRequest.id });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.approvalDecisions).toEqual(mixed);
    }
  });

  it("returns multiple send-back/resubmit rounds (SEND_BACK then RESUBMIT) in chronological order", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();

    const sendBack1 = {
      ...approveDecision,
      id: "decision-sendback-r1",
      decision: "SEND_BACK" as const,
      comment: "Fix quantities",
      decidedAt: new Date("2024-03-03T01:00:00.000Z"),
    };
    const resubmit1 = {
      ...approveDecision,
      id: "decision-resubmit-r1",
      approvalStepId: null,
      decision: "RESUBMIT" as const,
      decidedAt: new Date("2024-03-03T02:00:00.000Z"),
    };
    const sendBack2 = {
      ...approveDecision,
      id: "decision-sendback-r2",
      decision: "SEND_BACK" as const,
      comment: "Still wrong currency",
      decidedAt: new Date("2024-03-03T03:00:00.000Z"),
    };
    const resubmit2 = {
      ...approveDecision,
      id: "decision-resubmit-r2",
      approvalStepId: null,
      decision: "RESUBMIT" as const,
      decidedAt: new Date("2024-03-03T04:00:00.000Z"),
    };
    const rounds = [sendBack1, resubmit1, sendBack2, resubmit2];

    mock.enqueueResults([pendingRequest], rounds);

    const result = await run(mock.db, { approvalRequestId: pendingRequest.id });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.approvalDecisions).toEqual(rounds);
    }
    expect(mock.selects[1].sql).toMatch(/order by .*"decidedAt" asc/i);
  });

  it("returns an empty array when the request has no decisions yet", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([pendingRequest], []);

    const result = await run(mock.db, { approvalRequestId: pendingRequest.id });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.approvalDecisions).toEqual([]);
    }
  });

  it("returns REQUEST_NOT_FOUND when the request does not exist", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await run(mock.db, { approvalRequestId: "nonexistent-request" });

    expect(result.ok).toBe(false);
    if (!result.ok) {
      expect(result.error).toBeInstanceOf(RequestNotFoundError);
    }
  });
});
