/**
 * F2 — the web client routing + pairing flow:
 *  - not paired ⇒ pairing screen; wrong code ⇒ clear error, stays on pairing;
 *  - successful pair ⇒ read-only workspace (project list + open + snapshot);
 *  - a 401 (session.notifyUnauthorized) ⇒ back to pairing.
 */
import { describe, it, expect } from "vitest";
import { act, fireEvent, render, screen, waitFor } from "@testing-library/react";

import type { Gateways } from "@/ports";
import { DIProvider } from "@/app/di";
import { createMockGateways, MockWorkStateGateway } from "@/adapters/mock";
import { WebSession } from "@/adapters/http";
import type { FetchLike } from "@/adapters/http/httpInvoker";
import type { FlagStore } from "@/adapters/http/webSession";
import { WebApp } from "./WebApp";

function memStore(): FlagStore {
  const map = new Map<string, string>();
  return {
    getItem: (k) => map.get(k) ?? null,
    setItem: (k, v) => void map.set(k, v),
    removeItem: (k) => void map.delete(k),
  };
}

const okFetch: FetchLike = async () => ({
  ok: true,
  status: 200,
  json: async () => ({ ok: true }),
  text: async () => "{}",
});

/** Records fetch calls so a test can assert the logout POST. */
function recordingFetch(): { fetchImpl: FetchLike; calls: string[] } {
  const calls: string[] = [];
  const fetchImpl: FetchLike = async (url) => {
    calls.push(url);
    return { ok: true, status: 200, json: async () => ({ ok: true }), text: async () => "{}" };
  };
  return { fetchImpl, calls };
}

const rejectFetch: FetchLike = async () => ({
  ok: false,
  status: 401,
  json: async () => ({ code: "INVALID", message: "bad" }),
  text: async () => "{}",
});

/** Mock gateways seeded with one project + a work-state snapshot. */
async function seededGateways(): Promise<Gateways> {
  const gateways = createMockGateways();
  const project = await gateways.project.createProject("Demo", "/srv/demo");
  (gateways.workState as MockWorkStateGateway)._setProjectWorkState(project.id, {
    agents: [
      { agentId: "a1", name: "Archi", profileId: "p1", busy: { state: "idle" }, tickets: [] },
    ],
    conversations: [],
  });
  return gateways;
}

function renderWebApp(session: WebSession, gateways: Gateways) {
  return render(
    <DIProvider gateways={gateways}>
      <WebApp session={session} />
    </DIProvider>,
  );
}

describe("WebApp pairing routing", () => {
  it("shows the pairing screen when not paired", async () => {
    const session = new WebSession({ baseUrl: "https://h", fetchImpl: okFetch, store: memStore() });
    renderWebApp(session, await seededGateways());

    expect(screen.getByText("Appairer cet appareil")).toBeTruthy();
    expect(screen.queryByText("Projets")).toBeNull();
  });

  it("advances to the read-only workspace after a successful pair", async () => {
    const session = new WebSession({ baseUrl: "https://h", fetchImpl: okFetch, store: memStore() });
    renderWebApp(session, await seededGateways());

    fireEvent.change(screen.getByLabelText("Code d'appairage"), { target: { value: "4821-93" } });
    fireEvent.click(screen.getByRole("button", { name: "Appairer" }));

    expect(await screen.findByText("Projets")).toBeTruthy();
    expect(await screen.findByText("Demo")).toBeTruthy();
    expect(session.isPaired()).toBe(true);
  });

  it("shows a clear error on a wrong code and stays on pairing", async () => {
    const session = new WebSession({ baseUrl: "https://h", fetchImpl: rejectFetch, store: memStore() });
    renderWebApp(session, await seededGateways());

    fireEvent.change(screen.getByLabelText("Code d'appairage"), { target: { value: "nope" } });
    fireEvent.click(screen.getByRole("button", { name: "Appairer" }));

    expect(await screen.findByTestId("pairing-error")).toBeTruthy();
    expect(screen.queryByText("Projets")).toBeNull();
    expect(session.isPaired()).toBe(false);
  });

  it("opens a project read-only and renders the work-state snapshot", async () => {
    const session = new WebSession({ baseUrl: "https://h", fetchImpl: okFetch, store: memStore() });
    session.markPaired();
    renderWebApp(session, await seededGateways());

    // Workspace is shown directly (already paired); open the seeded project.
    fireEvent.click(await screen.findByText("Demo"));

    const snapshot = await screen.findByTestId("web-workstate");
    expect(snapshot.textContent).toContain("Archi");
    expect(snapshot.textContent).toContain("Idle");
  });

  it("opens a live agent cell for a work-state agent (F4 affordance)", async () => {
    const session = new WebSession({ baseUrl: "https://h", fetchImpl: okFetch, store: memStore() });
    session.markPaired();
    renderWebApp(session, await seededGateways());

    fireEvent.click(await screen.findByText("Demo"));
    await screen.findByTestId("web-workstate");

    // The per-agent "Ouvrir" affordance mounts the reusable TerminalView cell,
    // wired to the DI agent gateway (the CLI runs server-side).
    expect(screen.queryByTestId("web-agent-cell")).toBeNull();
    fireEvent.click(screen.getByRole("button", { name: "Ouvrir" }));
    expect(await screen.findByTestId("web-agent-cell")).toBeTruthy();
  });

  it("signs out via POST /api/logout and returns to pairing", async () => {
    const { fetchImpl, calls } = recordingFetch();
    const session = new WebSession({ baseUrl: "https://h", fetchImpl, store: memStore() });
    session.markPaired();
    renderWebApp(session, await seededGateways());

    expect(await screen.findByText("Projets")).toBeTruthy();

    fireEvent.click(screen.getByRole("button", { name: "Se déconnecter" }));

    await waitFor(() => expect(screen.getByText("Appairer cet appareil")).toBeTruthy());
    expect(calls).toContain("https://h/api/logout");
    expect(session.isPaired()).toBe(false);
    expect(screen.queryByText("Projets")).toBeNull();
  });

  it("returns to pairing when the session reports unauthorized (401)", async () => {
    const session = new WebSession({ baseUrl: "https://h", fetchImpl: okFetch, store: memStore() });
    session.markPaired();
    renderWebApp(session, await seededGateways());

    expect(await screen.findByText("Projets")).toBeTruthy();

    act(() => session.notifyUnauthorized());

    await waitFor(() => expect(screen.getByText("Appairer cet appareil")).toBeTruthy());
    expect(screen.queryByText("Projets")).toBeNull();
  });
});
