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

const { mockApi } = vi.hoisted(() => ({
  mockApi: {
    get: vi.fn(),
    post: vi.fn(),
    patch: vi.fn(),
    put: vi.fn(),
    delete: vi.fn(),
  },
}));

vi.mock("$lib/optima-api/axios", () => ({
  default: mockApi,
  api: mockApi,
}));

import { company } from "./companies";
import { credential } from "./credentials";
import { credentialType } from "./credentialTypes";
import { permission } from "./permissions";
import { procurement } from "./procurement";
import { role } from "./roles";
import { sales } from "./sales";
import { unifi } from "./unifi";
import { users } from "./users";

describe("optima api modules", () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it("company.fetchMany sends search params", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await company.fetchMany("token", 2, "acme", 50);

    expect(mockApi.get).toHaveBeenCalledWith("/v1/company/companies", {
      params: { page: 2, rpp: 50, search: "acme" },
      headers: { Authorization: "Bearer token" },
    });
  });

  it("company.count returns count", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { count: 17 } } });

    const count = await company.count("token");

    expect(count).toBe(17);
  });

  it("credential.fetch and delete call expected routes", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "cred-1" } } });
    mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });

    await credential.fetch("token", "cred-1");
    await credential.delete("token", "cred-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1",
      {
        headers: { Authorization: "Bearer token" },
      },
    );
    expect(mockApi.delete).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1",
      {
        headers: { Authorization: "Bearer token" },
      },
    );
  });

  it("credential.create posts payload", async () => {
    const payload = {
      name: "VPN",
      notes: "notes",
      typeId: "type-1",
      companyId: "company-1",
      fields: [],
    };
    mockApi.post.mockResolvedValueOnce({ data: { data: payload } });

    await credential.create("token", payload as any);

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/credential/credentials",
      payload,
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("credential.updateFields wraps fields payload", async () => {
    const fields = [{ id: "f1", name: "Username" }];
    mockApi.put.mockResolvedValueOnce({ data: { ok: true } });

    await credential.updateFields("token", "cred-1", fields as any);

    expect(mockApi.put).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1/fields",
      { fields },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("credential sub-credential methods call expected endpoints", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });
    mockApi.post.mockResolvedValueOnce({ data: { ok: true } });
    mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });

    await credential.fetchSubCredentials("token", "cred-1");
    await credential.addSubCredential("token", "cred-1", {
      fieldId: "fid",
      name: "Sub",
      fields: [{ fieldId: "f2", value: "v" }],
    });
    await credential.removeSubCredential("token", "cred-1", "sub-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1/sub-credentials",
      { headers: { Authorization: "Bearer token" } },
    );
    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1/sub-credentials",
      { fieldId: "fid", name: "Sub", fields: [{ fieldId: "f2", value: "v" }] },
      { headers: { Authorization: "Bearer token" } },
    );
    expect(mockApi.delete).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1/sub-credentials/sub-1",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("credentialType create and delete use identifier path", async () => {
    mockApi.post.mockResolvedValueOnce({ data: { ok: true } });
    mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });

    await credentialType.create("token", {
      name: "Router",
      permissionScope: "router",
      fields: [],
    } as any);
    await credentialType.delete("token", "ctype-1");

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/credential-type",
      { name: "Router", permissionScope: "router", fields: [] },
      { headers: { Authorization: "Bearer token" } },
    );
    expect(mockApi.delete).toHaveBeenCalledWith("/v1/credential-type/ctype-1", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("permission module endpoints are correct", async () => {
    mockApi.get.mockResolvedValue({ data: { data: [] } });

    await permission.fetchCategorized("token");
    await permission.fetchFlat("token");
    await permission.fetchByCategory("token", "company");

    expect(mockApi.get).toHaveBeenNthCalledWith(1, "/v1/permissions", {
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.get).toHaveBeenNthCalledWith(2, "/v1/permissions/nodes", {
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.get).toHaveBeenNthCalledWith(3, "/v1/permissions/company", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("procurement methods build params and count correctly", async () => {
    mockApi.get
      .mockResolvedValueOnce({ data: { data: [] } })
      .mockResolvedValueOnce({ data: { data: { count: 4 } } });

    await procurement.fetchMany(
      "token",
      3,
      { search: "switch", includeInactive: true },
      10,
    );
    const count = await procurement.count("token", true);

    expect(mockApi.get).toHaveBeenNthCalledWith(1, "/v1/procurement/items", {
      params: { page: 3, rpp: 10, search: "switch", includeInactive: true },
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.get).toHaveBeenNthCalledWith(2, "/v1/procurement/count", {
      params: { activeOnly: "true" },
      headers: { Authorization: "Bearer token" },
    });
    expect(count).toBe(4);
  });

  it("procurement link and unlink post target id", async () => {
    mockApi.post.mockResolvedValue({ data: { ok: true } });

    await procurement.linkItem("token", "item-a", "item-b");
    await procurement.unlinkItem("token", "item-a", "item-b");

    expect(mockApi.post).toHaveBeenNthCalledWith(
      1,
      "/v1/procurement/items/item-a/link",
      { targetId: "item-b" },
      { headers: { Authorization: "Bearer token" } },
    );
    expect(mockApi.post).toHaveBeenNthCalledWith(
      2,
      "/v1/procurement/items/item-a/unlink",
      { targetId: "item-b" },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("procurement.fetchMany passes all CatalogItemFilters as params", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await procurement.fetchMany("token", 1, {
      search: "cable",
      category: "Networking",
      subcategory: "Ethernet",
      group: "Cat6",
      manufacturer: "Ubiquiti",
      ecosystem: "unifi",
      inStock: true,
      minPrice: 10,
      maxPrice: 500,
      includeInactive: true,
    });

    expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/items", {
      params: {
        page: 1,
        rpp: 30,
        search: "cable",
        category: "Networking",
        subcategory: "Ethernet",
        group: "Cat6",
        manufacturer: "Ubiquiti",
        ecosystem: "unifi",
        inStock: true,
        minPrice: 10,
        maxPrice: 500,
        includeInactive: true,
      },
      headers: { Authorization: "Bearer token" },
    });
  });

  it("procurement.fetchMany omits filters that are not set", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await procurement.fetchMany("token", 2, { category: "Audio" }, 15);

    expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/items", {
      params: { page: 2, rpp: 15, category: "Audio" },
      headers: { Authorization: "Bearer token" },
    });
  });

  it("procurement.fetchCategories returns category tree", async () => {
    const tree = { categories: [{ name: "Net" }], ecosystems: [] };
    mockApi.get.mockResolvedValueOnce({ data: { data: tree } });

    const result = await procurement.fetchCategories("token");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/categories", {
      headers: { Authorization: "Bearer token" },
    });
    expect(result).toEqual(tree);
  });

  it("procurement.fetchFilters passes optional params", async () => {
    const filterValues = {
      categories: ["A"],
      subcategories: ["B"],
      manufacturers: ["C"],
    };
    mockApi.get.mockResolvedValueOnce({ data: { data: filterValues } });

    const result = await procurement.fetchFilters("token", {
      category: "Networking",
      subcategory: "Switches",
      includeInactive: true,
    });

    expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/filters", {
      params: {
        category: "Networking",
        subcategory: "Switches",
        includeInactive: "true",
      },
      headers: { Authorization: "Bearer token" },
    });
    expect(result).toEqual(filterValues);
  });

  it("procurement.fetchFilters works with no options", async () => {
    const filterValues = {
      categories: [],
      subcategories: [],
      manufacturers: [],
    };
    mockApi.get.mockResolvedValueOnce({ data: { data: filterValues } });

    const result = await procurement.fetchFilters("token");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/filters", {
      params: {},
      headers: { Authorization: "Bearer token" },
    });
    expect(result).toEqual(filterValues);
  });

  it("role add and remove permissions include payload", async () => {
    mockApi.post.mockResolvedValueOnce({ data: { ok: true } });
    mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });

    await role.addPermissions("token", "role-1", ["a", "b"]);
    await role.removePermissions("token", "role-1", ["a"]);

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/role/role-1/permissions",
      { permissions: ["a", "b"] },
      { headers: { Authorization: "Bearer token" } },
    );
    expect(mockApi.delete).toHaveBeenCalledWith("/v1/role/role-1/permissions", {
      headers: { Authorization: "Bearer token" },
      data: { permissions: ["a"] },
    });
  });

  it("sales.fetchMany includes includeClosed and search params", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await sales.fetchMany("token", 1, "fiber", 25, true);

    expect(mockApi.get).toHaveBeenCalledWith("/v1/sales/opportunities", {
      params: { page: 1, rpp: 25, search: "fiber", includeClosed: true },
      headers: { Authorization: "Bearer token" },
    });
  });

  it("sales.fetchOpportunityTypes calls expected endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await sales.fetchOpportunityTypes("token");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/sales/opportunity-types", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("sales.fetchOne encodes identifier in URL", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "opp-1" } } });

    await sales.fetchOne("token", "opp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1",
      {
        params: {},
        headers: { Authorization: "Bearer token" },
      },
    );
  });

  it("sales.fetchForecasts calls forecasts endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await sales.fetchForecasts("token", "opp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/forecasts",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.fetchProducts calls products endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await sales.fetchProducts("token", "opp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.fetchNotes calls notes endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await sales.fetchNotes("token", "opp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/notes",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.createNote posts note payload", async () => {
    mockApi.post.mockResolvedValueOnce({ data: { id: 1, text: "Hello" } });

    await sales.createNote("token", "opp-1", {
      text: "Hello",
      flagged: true,
    });

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/notes",
      { text: "Hello", flagged: true },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.updateNote patches with noteId in URL", async () => {
    mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });

    await sales.updateNote("token", "opp-1", 42, { text: "Updated" });

    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/notes/42",
      { text: "Updated" },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.deleteNote calls delete with noteId in URL", async () => {
    mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });

    await sales.deleteNote("token", "opp-1", 42);

    expect(mockApi.delete).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/notes/42",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.fetchContacts calls contacts endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await sales.fetchContacts("token", "opp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/contacts",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.sequenceProducts patches with ordered IDs", async () => {
    mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });

    await sales.sequenceProducts("token", "opp-1", [3, 1, 2]);

    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products/sequence",
      { orderedIds: [3, 1, 2] },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.refreshOpportunity posts to refresh endpoint", async () => {
    mockApi.post.mockResolvedValueOnce({ data: { ok: true } });

    await sales.refreshOpportunity("token", "opp-1");

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/refresh",
      {},
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.fetchOne encodes special characters in identifier", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: {} } });

    await sales.fetchOne("token", "opp/special#1");

    expect(mockApi.get).toHaveBeenCalledWith(
      `/v1/sales/opportunities/opportunity/${encodeURIComponent("opp/special#1")}`,
      { params: {}, headers: { Authorization: "Bearer token" } },
    );
  });

  it("users module uses expected endpoints", async () => {
    mockApi.get.mockResolvedValue({ data: { data: [] } });
    mockApi.post.mockResolvedValueOnce({ data: { data: { results: [] } } });
    mockApi.patch.mockResolvedValueOnce({ data: { data: {} } });
    mockApi.delete.mockResolvedValueOnce({ data: { data: {} } });

    await users.fetchAll("token");
    await users.fetch("token", "user-1");
    await users.fetchRoles("token", "user-1");
    await users.checkPermissions("token", "user-1", ["x"]);
    await users.update("token", "user-1", { name: "New Name" });
    await users.delete("token", "user-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/user/users", {
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.get).toHaveBeenCalledWith("/v1/user/users/user-1", {
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.get).toHaveBeenCalledWith("/v1/user/users/user-1/roles", {
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/user/users/user-1/check-permission",
      { permissions: ["x"] },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("unifi module hits expected endpoints for site and wifi actions", async () => {
    mockApi.get.mockResolvedValue({ data: { data: [] } });
    mockApi.post.mockResolvedValue({ data: { ok: true } });
    mockApi.patch.mockResolvedValue({ data: { ok: true } });

    await unifi.fetchSites("token");
    await unifi.syncSites("token");
    await unifi.createSite("token", "HQ");
    await unifi.linkSite("token", "site-1", "company-1");
    await unifi.unlinkSite("token", "site-1");
    await unifi.fetchSiteWifi("token", "site-1");
    await unifi.updateWifi("token", "site-1", "wlan-1", { name: "New" });
    await unifi.fetchPPSKs("token", "site-1", "wlan-1");
    await unifi.createPPSK("token", "site-1", "wlan-1", {
      key: "abc",
      name: "Staff",
    });

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/sites", {
      headers: { Authorization: "Bearer token" },
    });
    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/unifi/sites/sync",
      {},
      { headers: { Authorization: "Bearer token" } },
    );
    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/unifi/site/site-1/wifi/wlan-1",
      { name: "New" },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  // ── company: missing methods ──────────────────────────────────────────

  it("company.fetch retrieves a single company with options", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { id: "c1", name: "Acme" } });

    const result = await company.fetch("token", "c1", {
      includeAddress: true,
      includePrimaryContact: true,
      includeAllContacts: true,
    });

    expect(mockApi.get).toHaveBeenCalledWith("/v1/company/companies/c1", {
      params: {
        includeAddress: "true",
        includePrimaryContact: "true",
        includeAllContacts: "true",
      },
      headers: { Authorization: "Bearer token" },
    });
    expect(result).toEqual({ id: "c1", name: "Acme" });
  });

  it("company.fetch works without options", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { id: "c1" } });

    await company.fetch("token", "c1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/company/companies/c1", {
      params: {},
      headers: { Authorization: "Bearer token" },
    });
  });

  it("company.fetchConfigurations calls configurations endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await company.fetchConfigurations("token", "c1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/company/companies/c1/configurations",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  // ── credential: missing methods ───────────────────────────────────────

  it("credential.fetchByCompany calls company endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await credential.fetchByCompany("token", "comp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/credential/credentials/company/comp-1",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("credential.update patches credential", async () => {
    mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });

    await credential.update("token", "cred-1", {
      name: "New Name",
      notes: "Updated",
    });

    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1",
      { name: "New Name", notes: "Updated" },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("credential.fetchSecureValue calls secure-values endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: "secret" } });

    await credential.fetchSecureValue("token", "cred-1", "field-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/credential/credentials/cred-1/secure-values/field-1",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("credential.fetchValueTypes calls valuetypes endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: ["text", "password"] } });

    await credential.fetchValueTypes("token");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/credential/valuetypes", {
      headers: { Authorization: "Bearer token" },
    });
  });

  // ── credentialType: missing methods ───────────────────────────────────

  it("credentialType.fetchMany lists all types", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await credentialType.fetchMany("token");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/credential-type", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("credentialType.fetch retrieves a single type", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "ct-1" } } });

    await credentialType.fetch("token", "ct-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/credential-type/ct-1", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("credentialType.update patches a type", async () => {
    mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });

    await credentialType.update("token", "ct-1", { name: "Updated" });

    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/credential-type/ct-1",
      { name: "Updated" },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("credentialType.fetchCredentials retrieves credentials for a type", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await credentialType.fetchCredentials("token", "ct-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/credential-type/ct-1/credentials",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  // ── procurement: missing methods ──────────────────────────────────────

  it("procurement.fetch retrieves a single item", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "item-1" } } });

    await procurement.fetch("token", "item-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/items/item-1", {
      params: {},
      headers: { Authorization: "Bearer token" },
    });
  });

  it("procurement.fetch passes includeLinkedItems option", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "item-1" } } });

    await procurement.fetch("token", "item-1", { includeLinkedItems: true });

    expect(mockApi.get).toHaveBeenCalledWith("/v1/procurement/items/item-1", {
      params: { includeLinkedItems: "true" },
      headers: { Authorization: "Bearer token" },
    });
  });

  it("procurement.refreshInventory posts to refresh endpoint", async () => {
    mockApi.post.mockResolvedValueOnce({ data: { ok: true } });

    await procurement.refreshInventory("token", "item-1");

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/procurement/items/item-1/refresh-inventory",
      {},
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("procurement.fetchLinkedItems calls linked endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await procurement.fetchLinkedItems("token", "item-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/procurement/items/item-1/linked",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  // ── role: missing methods ─────────────────────────────────────────────

  it("role.fetchMany lists all roles", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await role.fetchMany("token");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/role", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("role.fetch retrieves a single role", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "r-1" } } });

    await role.fetch("token", "r-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/role/r-1", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("role.create posts role data", async () => {
    const data = { title: "Admin", moniker: "admin", permissions: ["a"] };
    mockApi.post.mockResolvedValueOnce({ data: { data } });

    await role.create("token", data as any);

    expect(mockApi.post).toHaveBeenCalledWith("/v1/role", data, {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("role.update patches a role", async () => {
    mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });

    await role.update("token", "r-1", { title: "Super Admin" });

    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/role/r-1",
      { title: "Super Admin" },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("role.delete removes a role", async () => {
    mockApi.delete.mockResolvedValueOnce({ data: { ok: true } });

    await role.delete("token", "r-1");

    expect(mockApi.delete).toHaveBeenCalledWith("/v1/role/r-1", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("role.fetchUsers lists users in a role", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await role.fetchUsers("token", "r-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/role/r-1/users", {
      headers: { Authorization: "Bearer token" },
    });
  });

  // ── sales: missing methods ────────────────────────────────────────────

  it("sales.fetchOne passes include params when provided", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "opp-1" } } });

    await sales.fetchOne("token", "opp-1", ["notes", "contacts", "products"]);

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1",
      {
        params: { include: "notes,contacts,products" },
        headers: { Authorization: "Bearer token" },
      },
    );
  });

  it("sales.addProduct posts product body", async () => {
    const body = { quantity: 3, revenue: 100, cost: 50 };
    mockApi.post.mockResolvedValueOnce({ data: { data: body } });

    await sales.addProduct("token", "opp-1", body);

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products",
      body,
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.addSpecialOrder posts special order body", async () => {
    const body = { desc: "Custom widget", price: 250 };
    mockApi.post.mockResolvedValueOnce({ data: { data: body } });

    await sales.addSpecialOrder("token", "opp-1", body);

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products/special-order",
      body,
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.fetchLaborOptions calls labor options endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({
      data: { data: { defaults: {}, options: {} } },
    });

    await sales.fetchLaborOptions("token", "opp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products/labor/options",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.addLabor posts labor body", async () => {
    const body = { laborStyle: "field" as const, hours: 8 };
    mockApi.post.mockResolvedValueOnce({ data: { ok: true } });

    await sales.addLabor("token", "opp-1", body);

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products/labor",
      body,
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.editProduct patches product with ID", async () => {
    const body = { productDescription: "Updated", quantity: 5 };
    mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });

    await sales.editProduct("token", "opp-1", 10, body);

    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products/10/edit",
      body,
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("sales.cancelProduct patches product cancellation", async () => {
    const body = { quantityCancelled: 2, cancellationReason: "Out of stock" };
    mockApi.patch.mockResolvedValueOnce({ data: { ok: true } });

    await sales.cancelProduct("token", "opp-1", 10, body);

    expect(mockApi.patch).toHaveBeenCalledWith(
      "/v1/sales/opportunities/opportunity/opp-1/products/10/cancel",
      body,
      { headers: { Authorization: "Bearer token" } },
    );
  });

  // ── unifi: missing methods ────────────────────────────────────────────

  it("unifi.fetchSite retrieves a single site", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: { id: "s-1" } } });

    await unifi.fetchSite("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/site/s-1", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("unifi.fetchCompanySites retrieves sites for a company", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await unifi.fetchCompanySites("token", "comp-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/company/companies/comp-1/unifi/sites",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("unifi.fetchSiteOverview calls overview endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: {} } });

    await unifi.fetchSiteOverview("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/site/s-1/overview", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("unifi.fetchSiteDevices calls devices endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await unifi.fetchSiteDevices("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/site/s-1/devices", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("unifi.fetchSiteNetworks calls networks endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await unifi.fetchSiteNetworks("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/site/s-1/networks", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("unifi.fetchWlanGroups calls wlan-groups endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await unifi.fetchWlanGroups("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/site/s-1/wlan-groups", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("unifi.fetchApGroups calls ap-groups endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await unifi.fetchApGroups("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/site/s-1/ap-groups", {
      headers: { Authorization: "Bearer token" },
    });
  });

  it("unifi.fetchAccessPoints calls access-points endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await unifi.fetchAccessPoints("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/unifi/site/s-1/access-points",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("unifi.fetchSpeedProfiles calls speed-profiles endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: [] } });

    await unifi.fetchSpeedProfiles("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith(
      "/v1/unifi/site/s-1/speed-profiles",
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("unifi.createSpeedProfile posts speed profile data", async () => {
    mockApi.post.mockResolvedValueOnce({ data: { ok: true } });

    await unifi.createSpeedProfile("token", "s-1", {
      name: "Fast",
      downloadLimitKbps: 100000,
      uploadLimitKbps: 50000,
    });

    expect(mockApi.post).toHaveBeenCalledWith(
      "/v1/unifi/site/s-1/speed-profiles",
      { name: "Fast", downloadLimitKbps: 100000, uploadLimitKbps: 50000 },
      { headers: { Authorization: "Bearer token" } },
    );
  });

  it("unifi.fetchWifiLimits calls wifi-limits endpoint", async () => {
    mockApi.get.mockResolvedValueOnce({ data: { data: {} } });

    await unifi.fetchWifiLimits("token", "s-1");

    expect(mockApi.get).toHaveBeenCalledWith("/v1/unifi/site/s-1/wifi-limits", {
      headers: { Authorization: "Bearer token" },
    });
  });
});
