/**
 * Ticket #82 — the "Tools MCP IdeA" tab of the project `Permissions` panel.
 *
 * Pins the UX contract from carnet #82 (Conception UX/F): an agent without an
 * override shows it inherits the project default with non-editable controls
 * until `Créer un override`; creating one prefills the effective allowlist;
 * toggling a tool updates the visible summary; saving calls the backend
 * command with the exact resulting allowlist; and an unsaved draft is
 * protected when the user switches target.
 */
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react";

import {
  MockAgentGateway,
  MockPermissionGateway,
  MockProfileGateway,
} from "@/adapters/mock";
import { DIProvider } from "@/app/di";
import type { Gateways } from "@/ports";
import { PermissionsPanel } from "./PermissionsPanel";

const PROJECT_ID = "proj-mcp-permissions-test";

async function renderPanel() {
  const agent = new MockAgentGateway();
  const permission = new MockPermissionGateway();
  const created = await agent.createAgent(PROJECT_ID, {
    name: "DevFrontend",
    profileId: "p1",
  });
  const gateways = {
    agent,
    permission,
    profile: new MockProfileGateway(),
  } as unknown as Gateways;

  render(
    <DIProvider gateways={gateways}>
      <PermissionsPanel projectId={PROJECT_ID} />
    </DIProvider>,
  );

  await waitFor(() => {
    expect(screen.getByText("DevFrontend")).toBeTruthy();
  });

  return { agent, permission, agentId: created.id };
}

/** Switches to the "Tools MCP IdeA" tab and waits past the doc load. */
async function openMcpToolsTab() {
  fireEvent.click(screen.getByRole("tab", { name: "Tools MCP IdeA" }));
  await screen.findByRole("navigation", { name: "cibles des permissions MCP" });
}

async function selectAgentTarget(name: string) {
  const nav = screen.getByRole("navigation", { name: "cibles des permissions MCP" });
  fireEvent.click(within(nav).getByRole("button", { name: new RegExp(name) }));
  await waitFor(() => {
    expect(screen.getByRole("heading", { name: `Tools MCP IdeA — ${name}` })).toBeTruthy();
  });
}

describe("PermissionsPanel — Tools MCP IdeA tab", () => {
  it("has an accessible tablist and defaults to Système", async () => {
    await renderPanel();

    const tablist = screen.getByRole("tablist", { name: "Permissions" });
    const tabs = within(tablist).getAllByRole("tab");
    expect(tabs.map((t) => t.textContent)).toEqual(["Système", "Tools MCP IdeA"]);
    expect(screen.getByRole("tab", { name: "Système" }).getAttribute("aria-selected")).toBe(
      "true",
    );
  });

  it("an agent without an override shows it inherits the project default, read-only", async () => {
    await renderPanel();
    await openMcpToolsTab();

    const nav = screen.getByRole("navigation", { name: "cibles des permissions MCP" });
    const agentButton = within(nav).getByRole("button", { name: /DevFrontend/ });
    expect(within(agentButton).getByText("Hérité")).toBeTruthy();

    await selectAgentTarget("DevFrontend");

    expect(screen.getByText("Hérite du défaut projet")).toBeTruthy();
    expect(screen.getByRole("button", { name: "Créer un override" })).toBeTruthy();

    // Read groups are collapsed by default in agent editing (carnet #82) — expand it.
    fireEvent.click(screen.getByRole("button", { name: /Lecture tickets/ }));

    // A canonical read-only tool is shown checked but disabled (inherited display).
    const readCheckbox = screen.getByRole("checkbox", {
      name: /idea_ticket_read$/,
    }) as HTMLInputElement;
    expect(readCheckbox.checked).toBe(true);
    expect(readCheckbox.disabled).toBe(true);

    // A write tool is shown unchecked (canonical read-only fallback denies it) and disabled.
    const writeCheckbox = screen.getByRole("checkbox", {
      name: /idea_ticket_update_carnet$/,
    }) as HTMLInputElement;
    expect(writeCheckbox.checked).toBe(false);
    expect(writeCheckbox.disabled).toBe(true);

    // No Save action is meaningfully available before creating an override.
    expect(screen.getByRole("button", { name: "Enregistrer" })).toHaveProperty("disabled", true);
  });

  it("creating an override prefills the draft with the current effective allowlist", async () => {
    await renderPanel();
    await openMcpToolsTab();
    await selectAgentTarget("DevFrontend");

    fireEvent.click(screen.getByRole("button", { name: "Créer un override" }));

    expect(screen.getByText("Override personnalisé")).toBeTruthy();

    // Read groups are collapsed by default in agent editing (carnet #82) — expand it.
    fireEvent.click(screen.getByRole("button", { name: /Lecture tickets/ }));

    // Prefilled from the effective (inherited) allowlist: canonical read-only
    // tools checked and now editable.
    const readCheckbox = screen.getByRole("checkbox", {
      name: /idea_ticket_read$/,
    }) as HTMLInputElement;
    expect(readCheckbox.checked).toBe(true);
    expect(readCheckbox.disabled).toBe(false);

    const writeCheckbox = screen.getByRole("checkbox", {
      name: /idea_ticket_update_carnet$/,
    }) as HTMLInputElement;
    expect(writeCheckbox.checked).toBe(false);

    // No edit yet: nothing to save.
    expect(screen.getByRole("button", { name: "Enregistrer" })).toHaveProperty("disabled", true);
  });

  it("checking a write tool updates the effective summary and unsaved-changes flag", async () => {
    await renderPanel();
    await openMcpToolsTab();
    await selectAgentTarget("DevFrontend");
    fireEvent.click(screen.getByRole("button", { name: "Créer un override" }));

    expect(screen.getByText(/9 lecture autorisés · 0 écriture autorisé/)).toBeTruthy();

    const writeCheckbox = screen.getByRole("checkbox", {
      name: /idea_ticket_update_carnet$/,
    });
    fireEvent.click(writeCheckbox);

    expect(screen.getByText(/9 lecture autorisés · 1 écriture autorisé/)).toBeTruthy();
    expect(screen.getByText("Modifications non enregistrées")).toBeTruthy();
    expect(screen.getByText("Ajouté")).toBeTruthy();
  });

  it("saving calls updateAgentMcpToolPermissions with the exact resulting allowlist", async () => {
    const { permission, agentId } = await renderPanel();
    const spy = vi.spyOn(permission, "updateAgentMcpToolPermissions");
    await openMcpToolsTab();
    await selectAgentTarget("DevFrontend");
    fireEvent.click(screen.getByRole("button", { name: "Créer un override" }));

    fireEvent.click(screen.getByRole("checkbox", { name: /idea_ticket_update_carnet$/ }));
    fireEvent.click(screen.getByRole("button", { name: "Enregistrer" }));

    await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
    const [calledProjectId, calledAgentId, calledPolicy] = spy.mock.calls[0]!;
    expect(calledProjectId).toBe(PROJECT_ID);
    expect(calledAgentId).toBe(agentId);
    expect(calledPolicy?.allowedTools).toEqual(
      expect.arrayContaining([
        "idea_list_agents",
        "idea_context_read",
        "idea_memory_read",
        "idea_skill_read",
        "idea_workstate_read",
        "idea_ticket_read",
        "idea_ticket_list",
        "idea_ticket_read_carnet",
        "idea_sprint_list",
        "idea_ticket_update_carnet",
      ]),
    );
    expect(calledPolicy?.allowedTools).toHaveLength(10);

    // Persisted: the left-column badge flips to Override, and Réinitialiser appears.
    const nav = screen.getByRole("navigation", { name: "cibles des permissions MCP" });
    const agentButton = within(nav).getByRole("button", { name: /DevFrontend/ });
    expect(within(agentButton).getByText("Override")).toBeTruthy();
    expect(screen.getByRole("button", { name: "Réinitialiser l'override" })).toBeTruthy();
  });

  describe("draft protection on target change", () => {
    const originalConfirm = window.confirm;
    beforeEach(() => {
      window.confirm = vi.fn();
    });
    afterEach(() => {
      window.confirm = originalConfirm;
    });

    it("asks for confirmation, and keeps the draft when the user cancels", async () => {
      await renderPanel();
      await openMcpToolsTab();
      await selectAgentTarget("DevFrontend");
      fireEvent.click(screen.getByRole("button", { name: "Créer un override" }));
      fireEvent.click(screen.getByRole("checkbox", { name: /idea_ticket_update_carnet$/ }));

      (window.confirm as ReturnType<typeof vi.fn>).mockReturnValue(false);

      const nav = screen.getByRole("navigation", { name: "cibles des permissions MCP" });
      fireEvent.click(within(nav).getByRole("button", { name: /^Défaut projet/ }));

      expect(window.confirm).toHaveBeenCalled();
      // Still on the agent editor, with the unsaved edit intact.
      expect(
        screen.getByRole("heading", { name: "Tools MCP IdeA — DevFrontend" }),
      ).toBeTruthy();
      expect(
        (screen.getByRole("checkbox", { name: /idea_ticket_update_carnet$/ }) as HTMLInputElement)
          .checked,
      ).toBe(true);
    });

    it("discards the draft and switches target when the user confirms", async () => {
      await renderPanel();
      await openMcpToolsTab();
      await selectAgentTarget("DevFrontend");
      fireEvent.click(screen.getByRole("button", { name: "Créer un override" }));
      fireEvent.click(screen.getByRole("checkbox", { name: /idea_ticket_update_carnet$/ }));

      (window.confirm as ReturnType<typeof vi.fn>).mockReturnValue(true);

      const nav = screen.getByRole("navigation", { name: "cibles des permissions MCP" });
      fireEvent.click(within(nav).getByRole("button", { name: /^Défaut projet/ }));

      expect(window.confirm).toHaveBeenCalled();
      expect(
        screen.getByRole("heading", { name: "Tools MCP IdeA — Défaut projet" }),
      ).toBeTruthy();
    });

    it("does not prompt when switching target with no unsaved changes", async () => {
      await renderPanel();
      await openMcpToolsTab();
      await selectAgentTarget("DevFrontend");

      const nav = screen.getByRole("navigation", { name: "cibles des permissions MCP" });
      fireEvent.click(within(nav).getByRole("button", { name: /^Défaut projet/ }));

      expect(window.confirm).not.toHaveBeenCalled();
      expect(
        screen.getByRole("heading", { name: "Tools MCP IdeA — Défaut projet" }),
      ).toBeTruthy();
    });
  });
});
