/**
 * Integration tests for GET /api/products/search.
 * Tests authorization, query validation, filtering, and Decimal serialisation.
 */
import { vi, describe, it, expect, beforeAll, beforeEach } from "vitest";

vi.mock("@/auth", () => ({ auth: vi.fn() }));

import { auth } from "@/auth";
import { NextRequest } from "next/server";
import { GET } from "@/app/api/products/search/route";
import { makeSession, getSeedUser } from "./helpers";

let techId: string;
let accountsId: string;

beforeAll(async () => {
  const [tech, acct] = await Promise.all([
    getSeedUser("tech@pelagia.local"),
    getSeedUser("accounts@pelagia.local"),
  ]);
  techId = tech.id;
  accountsId = acct.id;
});

function makeRequest(query: string) {
  return new NextRequest(`http://localhost/api/products/search?q=${encodeURIComponent(query)}`);
}

// ── Authorization ─────────────────────────────────────────────────────────────

describe("GET /api/products/search — authorization", () => {
  it("returns 401 for unauthenticated requests", async () => {
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(null);
    const res = await GET(makeRequest("oil"));
    expect(res.status).toBe(401);
  });

  it("TECHNICAL can search products", async () => {
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
    const res = await GET(makeRequest("oil"));
    expect(res.status).toBe(200);
  });

  it("ACCOUNTS can search products", async () => {
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(accountsId, "ACCOUNTS"));
    const res = await GET(makeRequest("oil"));
    expect(res.status).toBe(200);
  });
});

// ── Query validation ──────────────────────────────────────────────────────────

describe("GET /api/products/search — query validation", () => {
  beforeEach(() => {
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
  });

  it("returns empty array for query shorter than 2 chars", async () => {
    const res = await GET(makeRequest("a"));
    const data = await res.json();
    expect(data).toEqual([]);
  });

  it("returns empty array for empty query", async () => {
    const res = await GET(makeRequest(""));
    const data = await res.json();
    expect(data).toEqual([]);
  });

  it("returns results for query of exactly 2 chars", async () => {
    const res = await GET(makeRequest("oi"));
    const data = await res.json();
    expect(Array.isArray(data)).toBe(true);
  });
});

// ── Search behaviour ──────────────────────────────────────────────────────────

describe("GET /api/products/search — search behaviour", () => {
  beforeEach(() => {
    vi.mocked(auth as unknown as () => Promise<unknown>).mockResolvedValue(makeSession(techId, "TECHNICAL"));
  });

  it("finds products by name substring", async () => {
    const res = await GET(makeRequest("Gear Oil"));
    const data: { name: string }[] = await res.json();
    expect(data.some((p) => p.name.toLowerCase().includes("gear oil"))).toBe(true);
  });

  it("finds products by product code", async () => {
    const res = await GET(makeRequest("LUBE"));
    const data: { code: string }[] = await res.json();
    // search spans code/name/description, so assert the code matches are present (not that every hit is a code match)
    expect(data.some((p) => p.code.toUpperCase().includes("LUBE"))).toBe(true);
  });

  it("finds products by description text", async () => {
    const res = await GET(makeRequest("turbocharger"));
    const data: { description: string | null }[] = await res.json();
    expect(data.length).toBeGreaterThan(0);
    expect(data.some((p) => p.description?.toLowerCase().includes("turbocharger"))).toBe(true);
  });

  it("search is case-insensitive", async () => {
    const [upper, lower] = await Promise.all([
      GET(makeRequest("GEAR OIL")).then((r) => r.json()),
      GET(makeRequest("gear oil")).then((r) => r.json()),
    ]);
    expect(upper.length).toBe(lower.length);
  });

  it("returns at most 10 results", async () => {
    // Query a broad term likely to match many products
    const res = await GET(makeRequest("a"));
    const data: unknown[] = await res.json();
    expect(data.length).toBeLessThanOrEqual(10);
  });

  it("serialises lastPrice as a plain number, not a Decimal object", async () => {
    const res = await GET(makeRequest("Gear Oil"));
    const data: { lastPrice: unknown }[] = await res.json();
    const withPrice = data.find((p) => p.lastPrice !== null);
    if (withPrice) {
      expect(typeof withPrice.lastPrice).toBe("number");
    }
  });

  it("excludes inactive products from results", async () => {
    const { db } = await import("@/lib/db");
    // Deactivate a known product temporarily
    const product = await db.product.findFirst({
      where: { code: "LUBE-EP80W90", isActive: true },
    });
    if (!product) return;

    await db.product.update({ where: { id: product.id }, data: { isActive: false } });
    try {
      const res = await GET(makeRequest("EP 80W90"));
      const data: { code: string }[] = await res.json();
      expect(data.find((p) => p.code === "LUBE-EP80W90")).toBeUndefined();
    } finally {
      await db.product.update({ where: { id: product.id }, data: { isActive: true } });
    }
  });
});
