import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { render, screen, fireEvent, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import SearchPage from "./page";

const ORIGINAL_ENV = process.env;

beforeEach(() => {
  process.env = { ...ORIGINAL_ENV, NEXT_PUBLIC_MAIL_TENANT_SLUG: "acme" };
});

afterEach(() => {
  process.env = ORIGINAL_ENV;
  vi.restoreAllMocks();
});

// SRC-06: SearchPage ruft jetzt sowohl /api/search als auch /api/facets
// auf (Promise.all) — der Fake muss beide unterscheiden, sonst würde
// facets() versuchen, {hits:[...]} als FacetsResponse zu lesen.
function mockSearchResponse(hits: unknown[], facetValues: Record<string, unknown[]> = {}) {
  vi.stubGlobal(
    "fetch",
    vi.fn().mockImplementation((url: string) => {
      if (url.includes("/api/facets")) {
        return Promise.resolve(new Response(JSON.stringify({ values: facetValues }), { status: 200 }));
      }
      return Promise.resolve(new Response(JSON.stringify({ hits }), { status: 200 }));
    })
  );
}

// Akzeptanzkriterium 1: Eingabe liefert Trefferliste mit hervorgehobenen
// Suchbegriffen im Snippet.
describe("SearchPage — Trefferliste mit Hervorhebung", () => {
  it("rendert Treffer mit <mark> um den hervorgehobenen Suchbegriff", async () => {
    mockSearchResponse([
      {
        messageId: "msg-1",
        subjectSnippet: "Quartalsbericht ⦃⦃Umsatz⦄⦄",
        bodySnippet: "hoher ⦃⦃Umsatz⦄⦄ im dritten Quartal",
        score: 100,
      },
    ]);

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "Umsatz" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);

    await waitFor(() => {
      const marks = document.querySelectorAll("mark");
      expect(marks.length).toBeGreaterThan(0);
    });
    const marks = Array.from(document.querySelectorAll("mark")).map((m) => m.textContent);
    expect(marks).toContain("Umsatz");
  });

  // Akzeptanzkriterium 3: leere Ergebnisse zeigen verständlichen Hinweis
  // statt leerer Fläche.
  it("zeigt bei leerem Ergebnis einen verständlichen Hinweis", async () => {
    mockSearchResponse([]);

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "nichts-vorhanden" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);

    await waitFor(() => {
      expect(screen.getByRole("status")).toHaveTextContent("Keine Treffer");
    });
  });

  // Pflichtprüfung 3: Sonderzeichen in der Suchanfrage bringen die Anzeige
  // nicht zum Absturz.
  it("wirft bei Sonderzeichen in der Suchanfrage keinen Fehler", async () => {
    mockSearchResponse([
      {
        messageId: "msg-2",
        subjectSnippet: `⦃⦃<script>alert(1)</script>⦄⦄ & "Zitat" 日本語`,
        bodySnippet: "",
        score: 1,
      },
    ]);

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), {
      target: { value: '"<script>" OR -Ümläüt 日本語' },
    });
    expect(() => fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!)).not.toThrow();

    await waitFor(() => {
      expect(document.querySelectorAll("mark").length).toBeGreaterThan(0);
    });
    // Kein <script>-Element im DOM entstanden (kein dangerouslySetInnerHTML,
    // Manticore-Highlight wird als reiner Text gerendert, nicht als HTML).
    expect(document.querySelectorAll("script").length).toBe(0);
  });

  // Akzeptanzkriterium 2: Klick auf Treffer öffnet die zugehörige Mail
  // direkt an der Fundstelle — hier geprüft über den erzeugten Link, da
  // die vollständige Mail-Ansicht (Inhaltsabruf per API) INT-01 vorbehalten
  // ist.
  it("verlinkt jeden Treffer auf die Mail-Detailseite mit Fundstellen-Anker", async () => {
    mockSearchResponse([
      { messageId: "msg-3", subjectSnippet: "Betreff", bodySnippet: "Text", score: 1 },
    ]);

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "Betreff" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);

    await waitFor(() => {
      const link = screen.getByRole("link");
      expect(link.getAttribute("href")).toMatch(/^\/mail\/msg-3\?.*#fundstelle$/);
    });
  });
});

// SRC-06: Facetten-UI & Filter-Chips.
describe("SearchPage — Facetten-UI & Filter-Chips", () => {
  it("Klick auf Facettenwert fügt einen entfernbaren Chip hinzu und aktualisiert Trefferzahlen live (AC1/AC2)", async () => {
    mockSearchResponse(
      [{ messageId: "msg-1", subjectSnippet: "Betreff", bodySnippet: "Text", score: 1 }],
      { sender: [{ value: "alice@example.com", count: 3 }], mailbox: [], attachment_type: [], tag: [] }
    );

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "Betreff" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);

    const facetButton = await screen.findByRole("button", { name: /alice@example\.com \(3\)/ });
    fireEvent.click(facetButton);

    const chip = await screen.findByRole("button", { name: /Filter Absender: alice@example\.com entfernen/ });
    expect(chip).toHaveTextContent("Absender: alice@example.com");

    // Trefferzahl-Update live: nach dem Filtern liefert der (gemockte)
    // Server jetzt einen anderen Wert für denselben Facettenwert.
    mockSearchResponse(
      [{ messageId: "msg-1", subjectSnippet: "Betreff", bodySnippet: "Text", score: 1 }],
      { sender: [{ value: "alice@example.com", count: 1 }], mailbox: [], attachment_type: [], tag: [] }
    );
    fireEvent.click(chip);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "Betreff" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);
    await screen.findByRole("button", { name: /alice@example\.com \(1\)/ });
  });

  it("Zurücksetzen aller Filter entfernt alle Chips mit einem Klick (AC3)", async () => {
    mockSearchResponse(
      [{ messageId: "msg-1", subjectSnippet: "Betreff", bodySnippet: "Text", score: 1 }],
      {
        sender: [{ value: "alice@example.com", count: 2 }],
        mailbox: [{ value: "inbox", count: 2 }],
        attachment_type: [],
        tag: [],
      }
    );

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "Betreff" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);

    fireEvent.click(await screen.findByRole("button", { name: /alice@example\.com \(2\)/ }));
    fireEvent.click(await screen.findByRole("button", { name: /inbox \(2\)/ }));
    await screen.findByRole("button", { name: /Filter Absender: alice@example\.com entfernen/ });
    await screen.findByRole("button", { name: /Filter Postfach: inbox entfernen/ });

    fireEvent.click(screen.getByRole("button", { name: "Alle Filter zurücksetzen" }));

    await waitFor(() => {
      expect(screen.queryByRole("button", { name: /Filter Absender/ })).toBeNull();
      expect(screen.queryByRole("button", { name: /Filter Postfach/ })).toBeNull();
    });
  });

  // Pflichtprüfung 2: Tastaturbedienbarkeit der Filter-Chips.
  it("Filter-Chip ist per Tastatur (Tab + Enter) auslösbar", async () => {
    const user = userEvent.setup();
    mockSearchResponse(
      [{ messageId: "msg-1", subjectSnippet: "Betreff", bodySnippet: "Text", score: 1 }],
      { sender: [{ value: "alice@example.com", count: 1 }], mailbox: [], attachment_type: [], tag: [] }
    );

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "Betreff" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);
    await user.click(await screen.findByRole("button", { name: /alice@example\.com \(1\)/ }));

    const chip = await screen.findByRole("button", { name: /Filter Absender: alice@example\.com entfernen/ });
    chip.focus();
    expect(document.activeElement).toBe(chip);

    await user.keyboard("{Enter}");

    await waitFor(() => {
      expect(screen.queryByRole("button", { name: /Filter Absender/ })).toBeNull();
    });
  });

  // Pflichtprüfung 3: Test mit vielen aktiven Filtern bleibt die Ansicht
  // übersichtlich — real mit 20 gleichzeitig aktiven Filtern geprüft,
  // jeder bleibt einzeln als eigener, erkennbarer Chip vorhanden (keine
  // Zusammenfassung/Verlust von Einträgen), kein Absturz.
  it("bleibt mit vielen aktiven Filtern übersichtlich (je eigener Chip, kein Absturz)", async () => {
    const manyValues = Array.from({ length: 20 }, (_, i) => ({ value: `tag-${i}`, count: i + 1 }));
    mockSearchResponse(
      [{ messageId: "msg-1", subjectSnippet: "Betreff", bodySnippet: "Text", score: 1 }],
      { sender: [], mailbox: [], attachment_type: [], tag: manyValues }
    );

    render(<SearchPage />);
    fireEvent.change(screen.getByLabelText("Suchbegriff"), { target: { value: "Betreff" } });
    fireEvent.submit(screen.getByLabelText("Suchbegriff").closest("form")!);

    for (let i = 0; i < manyValues.length; i++) {
      const facetButton = await screen.findByRole("button", { name: new RegExp(`tag-${i} \\(${i + 1}\\)`) });
      fireEvent.click(facetButton);
    }

    const chipRegion = await screen.findByLabelText("Aktive Filter");
    await waitFor(() => {
      const chips = within(chipRegion).getAllByRole("button", { name: /^Filter Tag: /i });
      expect(chips).toHaveLength(20);
    });
  });
});
