import { createKyselyMock } from "@tailor-platform/sdk/vitest";
import { describe, expect, it } from "vitest";
import type { Namespace } from "../generated/kysely-tailordb";
import { approvedRequest, pendingRequest, rejectedRequest } from "../testing/fixtures";
import { run } from "./listApprovalRequestsForApprover";

const userId = "user-manager-1";

describe("listApprovalRequestsForApprover", () => {
  it("returns requests where the user is a PENDING assignee on an IN_PROGRESS step with parent request PENDING", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ approvalRequestId: pendingRequest.id }], [pendingRequest]);

    const result = await run(mock.db, { userId });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([pendingRequest]);
      expect(result.value.hasNextPage).toBe(false);
    }
    expect(mock.selects[0].sql).toMatch(/"ApprovalStepAssignee"."userId"/);
    expect(mock.selects[0].parameters).toContain(userId);
    expect(mock.selects[0].sql).toMatch(/"ApprovalStepAssignee"."status"/);
    expect(mock.selects[0].parameters).toContain("PENDING");
    expect(mock.selects[0].sql).toMatch(/"ApprovalStep"."status"/);
    expect(mock.selects[0].parameters).toContain("IN_PROGRESS");
    expect(mock.selects[1].sql).toMatch(/"status"/);
    expect(mock.selects[1].parameters).toContain("PENDING");
  });

  it("excludes requests where the user's only assignee row has resolved (APPROVED, REJECTED, DELEGATED)", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([]);

    const result = await run(mock.db, { userId });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([]);
    }
    expect(mock.selects[0].sql).toMatch(/"ApprovalStepAssignee"."status"/);
    expect(mock.selects[0].parameters).toContain("PENDING");
  });

  it("excludes requests where the parent has terminated by WITHDRAW even though the StepAssignee row is still PENDING", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ approvalRequestId: pendingRequest.id }], []);

    const result = await run(mock.db, { userId });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([]);
    }
    expect(mock.selects[1].sql).toMatch(/"status"/);
    expect(mock.selects[1].parameters).toContain("PENDING");
  });

  it("excludes requests where the parent has terminated by CANCEL even though the StepAssignee row is still PENDING", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ approvalRequestId: pendingRequest.id }], []);

    const result = await run(mock.db, { userId });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([]);
    }
    expect(mock.selects[1].sql).toMatch(/"status"/);
    expect(mock.selects[1].parameters).toContain("PENDING");
  });

  it("defaults the status filter to PENDING when omitted", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ approvalRequestId: pendingRequest.id }], [pendingRequest]);

    await run(mock.db, { userId });

    expect(mock.selects[1].sql).toMatch(/"status"/);
    expect(mock.selects[1].parameters).toContain("PENDING");
  });

  it("accepts a non-default status (e.g. APPROVED) to surface historical requests the user worked on", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ approvalRequestId: approvedRequest.id }], [approvedRequest]);

    const result = await run(mock.db, { userId, status: "APPROVED" });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([approvedRequest]);
    }
    expect(mock.selects[1].sql).toMatch(/"status"/);
    expect(mock.selects[1].parameters).toContain("APPROVED");
  });

  it("accepts a non-default status (e.g. REJECTED) to surface historical requests the user worked on", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ approvalRequestId: rejectedRequest.id }], [rejectedRequest]);

    const result = await run(mock.db, { userId, status: "REJECTED" });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([rejectedRequest]);
    }
    expect(mock.selects[1].sql).toMatch(/"status"/);
    expect(mock.selects[1].parameters).toContain("REJECTED");
  });

  it("deduplicates requests where the user has multiple PENDING assignee rows on the same step", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults(
      [{ approvalRequestId: pendingRequest.id }, { approvalRequestId: pendingRequest.id }],
      [pendingRequest],
    );

    const result = await run(mock.db, { userId });

    expect(result.ok).toBe(true);
    if (result.ok) {
      expect(result.value.items).toEqual([pendingRequest]);
    }
    // Second-query IN filter should receive a single id (deduped)
    expect(mock.selects[1].sql).toMatch(/"id" in/);
    expect(mock.selects[1].parameters).toContain(pendingRequest.id);
    expect(mock.selects[1].parameters.filter((p) => p === pendingRequest.id)).toHaveLength(1);
  });

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

    const result = await run(mock.db, { userId });

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

  it("returns rows ordered by createdAt DESC", async () => {
    const mock = createKyselyMock<Namespace["main-db"]>();
    mock.enqueueResults([{ approvalRequestId: pendingRequest.id }], [pendingRequest]);

    await run(mock.db, { userId });

    expect(mock.selects[1].sql).toMatch(/order by .*"createdAt" desc/i);
  });
});
