import { describe, it, expect } from "vitest";

import type { TicketListQuery } from "@/ports";
import {
  listFiltersKey,
  parseTicketFilters,
  pickerFiltersKey,
  sanitizeListFilters,
  sanitizePickerFilters,
} from "./ticketFilterPersistence";

describe("ticketFilterPersistence keys", () => {
  it("scopes keys by project and surface (list vs picker disjoint)", () => {
    expect(listFiltersKey("p1")).toBe("tickets:listFilters:p1");
    expect(pickerFiltersKey("p1")).toBe("tickets:pickerFilters:p1");
    expect(listFiltersKey("p1")).not.toBe(pickerFiltersKey("p1"));
  });
});

describe("parseTicketFilters (validation)", () => {
  it("returns an empty query for non-objects / corrupt input", () => {
    expect(parseTicketFilters(null)).toEqual({});
    expect(parseTicketFilters(undefined)).toEqual({});
    expect(parseTicketFilters("nope")).toEqual({});
    expect(parseTicketFilters(42)).toEqual({});
    expect(parseTicketFilters([1, 2, 3])).toEqual({});
  });

  it("keeps only known statuses/priorities and drops unknown values", () => {
    const parsed = parseTicketFilters({
      statuses: ["open", "bogus", "QA", "open"],
      priorities: ["high", "nope"],
    });
    expect(parsed.statuses).toEqual(["open", "QA"]); // deduped, unknown dropped
    expect(parsed.priorities).toEqual(["high"]);
  });

  it("keeps valid text/sort/limit and rejects malformed ones", () => {
    expect(parseTicketFilters({ text: "boom" }).text).toBe("boom");
    expect(parseTicketFilters({ text: "" }).text).toBeUndefined();
    expect(
      parseTicketFilters({ sort: { field: "number", direction: "desc" } }).sort,
    ).toEqual({ field: "number", direction: "desc" });
    expect(
      parseTicketFilters({ sort: { field: "bogus", direction: "desc" } }).sort,
    ).toBeUndefined();
    expect(parseTicketFilters({ limit: 50 }).limit).toBe(50);
    expect(parseTicketFilters({ limit: -1 }).limit).toBeUndefined();
    expect(parseTicketFilters({ limit: 1.5 }).limit).toBeUndefined();
  });

  it("never restores the opaque cursor", () => {
    const parsed = parseTicketFilters({ text: "x", cursor: "opaque-token" });
    expect(parsed.text).toBe("x");
    expect("cursor" in parsed).toBe(false);
  });

  it("drops an obsolete assignee when it is not among the known agents", () => {
    const raw = { assignedAgentId: "gone" };
    // No roster provided → shape-only (assignee kept).
    expect(parseTicketFilters(raw).assignedAgentId).toBe("gone");
    // Roster provided and missing the id → assignee dropped.
    expect(
      parseTicketFilters(raw, { validAgentIds: new Set(["a1"]) }).assignedAgentId,
    ).toBeUndefined();
    // Roster provided and containing the id → kept.
    expect(
      parseTicketFilters(raw, { validAgentIds: new Set(["gone"]) })
        .assignedAgentId,
    ).toBe("gone");
  });

  it("keeps valid creator filters and drops obsolete creator agents", () => {
    expect(parseTicketFilters({ createdBy: { kind: "user" } }).createdBy).toEqual({
      kind: "user",
    });
    expect(
      parseTicketFilters({ createdBy: { kind: "agent", agentId: "a1" } }, {
        validAgentIds: new Set(["a1"]),
      }).createdBy,
    ).toEqual({ kind: "agent", agentId: "a1" });
    expect(
      parseTicketFilters({ createdBy: { kind: "agent", agentId: "gone" } }, {
        validAgentIds: new Set(["a1"]),
      }).createdBy,
    ).toBeUndefined();
  });
});

describe("sanitize (persistable subsets)", () => {
  const full: TicketListQuery = {
    text: "hi",
    statuses: ["open"],
    priorities: ["high"],
    assignedAgentId: "a1",
    createdBy: { kind: "agent", agentId: "a1" },
    sort: { field: "title", direction: "asc" },
    limit: 100,
    cursor: "opaque",
  };

  it("list keeps assignee + limit but never cursor", () => {
    const out = sanitizeListFilters(full)!;
    expect(out).toEqual({
      text: "hi",
      statuses: ["open"],
      priorities: ["high"],
      assignedAgentId: "a1",
      createdBy: { kind: "agent", agentId: "a1" },
      sort: { field: "title", direction: "asc" },
      limit: 100,
    });
    expect("cursor" in out).toBe(false);
  });

  it("picker drops assignee, limit AND cursor", () => {
    const out = sanitizePickerFilters(full)!;
    expect(out).toEqual({
      text: "hi",
      statuses: ["open"],
      priorities: ["high"],
      sort: { field: "title", direction: "asc" },
    });
    expect("assignedAgentId" in out).toBe(false);
    expect("createdBy" in out).toBe(false);
    expect("limit" in out).toBe(false);
    expect("cursor" in out).toBe(false);
  });

  it("returns null when nothing is worth persisting", () => {
    expect(sanitizeListFilters({})).toBeNull();
    expect(sanitizeListFilters({ cursor: "only-a-cursor" })).toBeNull();
    expect(sanitizePickerFilters({ assignedAgentId: "a1" })).toBeNull();
  });
});
