/**
 * L14 / lot C2 — embedder settings feature wired to the stateful
 * `MockEmbedderGateway` via the real `DIProvider` (same harness as
 * `memory.test.tsx`).
 *
 * Covers:
 * - the strategy list renders on an equal footing (None, ONNX, Ollama, API,
 *   Custom) with the "Recommended" badge on ONNX
 * - conditional fields per strategy (ONNX model picker + auto dimension;
 *   localServer endpoint; api endpoint + apiKeyEnv label; none → no fields)
 * - a strategy whose build flag is false is shown disabled with
 *   "not available in this build" (None stays available)
 * - Save calls the gateway with the right profile
 * - "Back to None" deletes the configured profiles
 *
 * Plus the active-tier transparency line in MemoryPanel (Naïve vs Vector).
 */

import { describe, it, expect, vi } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";

import { MockEmbedderGateway, MockMemoryGateway } from "@/adapters/mock";
import type { EmbedderEngines, EmbedderProfile } from "@/domain";
import type { Gateways } from "@/ports";
import { DIProvider } from "@/app/di";
import { MemoryPanel } from "@/features/memory";
import { EmbedderSettings } from "./EmbedderSettings";

function renderEmbedder(
  engines?: Partial<EmbedderEngines>,
  seed: EmbedderProfile[] = [],
) {
  const embedder = new MockEmbedderGateway(engines, seed);
  const gateways = { embedder } as unknown as Gateways;
  return {
    embedder,
    ...render(
      <DIProvider gateways={gateways}>
        <EmbedderSettings />
      </DIProvider>,
    ),
  };
}

async function waitForEmbedderIdle() {
  await waitFor(() => {
    expect(
      (screen.getByRole("button", { name: "back to none" }) as HTMLButtonElement)
        .disabled,
    ).toBe(false);
  });
}

describe("EmbedderSettings (with MockEmbedderGateway)", () => {
  it("renders the strategy list on an equal footing with a Recommended badge", async () => {
    renderEmbedder();
    await waitForEmbedderIdle();

    // Every strategy is offered.
    expect(screen.getByTestId("embedder-strategy-none")).toBeTruthy();
    expect(screen.getByTestId("embedder-strategy-localOnnx")).toBeTruthy();
    expect(screen.getByTestId("embedder-strategy-localServer")).toBeTruthy();
    expect(screen.getByTestId("embedder-strategy-api")).toBeTruthy();
    expect(screen.getByTestId("embedder-strategy-custom")).toBeTruthy();

    // ONNX carries the "Recommended" badge.
    expect(screen.getByText("Recommended")).toBeTruthy();

    // The change-takes-effect notice is visible.
    expect(screen.getByTestId("embedder-restart-note").textContent).toMatch(
      /next app start/i,
    );
  });

  it("pre-selects ONNX with the recommended model and auto-fills its dimension (384)", async () => {
    renderEmbedder();
    await waitForEmbedderIdle();

    // ONNX is the default cursor → its model picker is shown.
    const modelSelect = (await screen.findByLabelText(
      "onnx model",
    )) as HTMLSelectElement;
    expect(modelSelect.value).toBe("e5-small");

    // Dimension auto-filled from the model.
    expect((screen.getByLabelText("dimension") as HTMLInputElement).value).toBe(
      "384",
    );
  });

  it("shows endpoint for localServer and endpoint + apiKeyEnv (named, not the key) for api", async () => {
    renderEmbedder();
    await waitForEmbedderIdle();

    // localServer → endpoint, no apiKeyEnv.
    fireEvent.click(
      screen.getByRole("radio", { name: "Ollama (local server)" }),
    );
    await waitFor(() => expect(screen.getByLabelText("endpoint")).toBeTruthy());
    expect(screen.queryByLabelText("api key env var")).toBeNull();

    // api → endpoint + apiKeyEnv with an explicit "env var" label.
    fireEvent.click(screen.getByRole("radio", { name: "API" }));
    await waitFor(() =>
      expect(screen.getByLabelText("api key env var")).toBeTruthy(),
    );
    expect(screen.getByText(/environment variable/i)).toBeTruthy();
    expect(screen.getByText(/never the key itself/i)).toBeTruthy();
  });

  it("renders no conditional fields for None", async () => {
    renderEmbedder();
    await waitForEmbedderIdle();

    fireEvent.click(screen.getByRole("radio", { name: "None (naïve recall)" }));
    await waitFor(() =>
      expect(screen.queryByLabelText("dimension")).toBeNull(),
    );
    expect(screen.queryByLabelText("onnx model")).toBeNull();
    // No Save button in None mode (only "Back to None").
    expect(screen.queryByRole("button", { name: "save embedder" })).toBeNull();
  });

  it("disables strategies whose build flag is false with a 'not available' note", async () => {
    renderEmbedder({ vectorOnnxEnabled: false, vectorHttpEnabled: false });
    await waitForEmbedderIdle();

    const onnxRadio = screen.getByRole("radio", {
      name: "Local ONNX",
    }) as HTMLInputElement;
    const ollamaRadio = screen.getByRole("radio", {
      name: "Ollama (local server)",
    }) as HTMLInputElement;
    const apiRadio = screen.getByRole("radio", { name: "API" }) as HTMLInputElement;
    const noneRadio = screen.getByRole("radio", {
      name: "None (naïve recall)",
    }) as HTMLInputElement;

    expect(onnxRadio.disabled).toBe(true);
    expect(ollamaRadio.disabled).toBe(true);
    expect(apiRadio.disabled).toBe(true);
    // None is always available.
    expect(noneRadio.disabled).toBe(false);

    // The honesty note appears.
    expect(
      screen.getAllByText("not available in this build").length,
    ).toBeGreaterThan(0);
  });

  it("Save sends the recommended ONNX profile (e5-small, dim 384)", async () => {
    const { embedder } = renderEmbedder();
    const spy = vi.spyOn(embedder, "saveEmbedderProfile");
    await waitForEmbedderIdle();

    fireEvent.click(screen.getByRole("button", { name: "save embedder" }));

    await waitFor(() => expect(spy).toHaveBeenCalledTimes(1));
    const saved = spy.mock.calls[0][0];
    expect(saved.strategy).toBe("localOnnx");
    expect(saved.model).toBe("e5-small");
    expect(saved.dimension).toBe(384);
    expect(saved.id.length).toBeGreaterThan(0);
    expect(saved.name.length).toBeGreaterThan(0);
    // ONNX never carries an endpoint/apiKeyEnv.
    expect(saved.endpoint).toBeUndefined();
    expect(saved.apiKeyEnv).toBeUndefined();
  });

  it("Save for api sends endpoint + apiKeyEnv (the env var NAME)", async () => {
    const { embedder } = renderEmbedder();
    const spy = vi.spyOn(embedder, "saveEmbedderProfile");
    await waitForEmbedderIdle();

    fireEvent.click(screen.getByRole("radio", { name: "API" }));
    await waitFor(() =>
      expect(screen.getByLabelText("api key env var")).toBeTruthy(),
    );
    fireEvent.change(screen.getByLabelText("api key env var"), {
      target: { value: "MY_KEY_VAR" },
    });
    fireEvent.click(screen.getByRole("button", { name: "save embedder" }));

    await waitFor(() => expect(spy).toHaveBeenCalled());
    const saved = spy.mock.calls.at(-1)![0];
    expect(saved.strategy).toBe("api");
    expect(saved.apiKeyEnv).toBe("MY_KEY_VAR");
    expect(saved.endpoint).toBeTruthy();
  });

  it("'Back to None' deletes the configured profiles", async () => {
    const seed: EmbedderProfile[] = [
      { id: "onnx-local", name: "Local ONNX", strategy: "localOnnx", model: "e5-small", dimension: 384 },
    ];
    const { embedder } = renderEmbedder(undefined, seed);
    const spy = vi.spyOn(embedder, "deleteEmbedderProfile");
    await waitForEmbedderIdle();

    fireEvent.click(screen.getByRole("button", { name: "back to none" }));

    await waitFor(() => expect(spy).toHaveBeenCalledWith("onnx-local"));
    expect(await embedder.listEmbedderProfiles()).toHaveLength(0);
  });
});

// ---------------------------------------------------------------------------
// MemoryPanel — active recall tier transparency
// ---------------------------------------------------------------------------

describe("MemoryPanel active tier (transparency)", () => {
  function renderPanel(embedder: MockEmbedderGateway) {
    const gateways = {
      memory: new MockMemoryGateway(),
      embedder,
    } as unknown as Gateways;
    return render(
      <DIProvider gateways={gateways}>
        <MemoryPanel projectId="p" />
      </DIProvider>,
    );
  }

  it("shows 'Naïve (None)' when no embedder is configured", async () => {
    renderPanel(new MockEmbedderGateway());
    const tier = await screen.findByTestId("memory-tier");
    await waitFor(() => expect(tier.textContent).toMatch(/Naïve \(None\)/));
  });

  it("shows 'Vector — <strategy> <model>' when an embedder is configured", async () => {
    const embedder = new MockEmbedderGateway(undefined, [
      { id: "onnx-local", name: "Local ONNX", strategy: "localOnnx", model: "e5-small", dimension: 384 },
    ]);
    renderPanel(embedder);
    const tier = await screen.findByTestId("memory-tier");
    await waitFor(() => expect(tier.textContent).toMatch(/Vector — localOnnx e5-small/));
  });
});
