// @vitest-environment jsdom
//
// Regression tests for ConfigTab hermes-workspace UX (#1894 + #1900).
//
// All four bugs this suite pins hit the same workspace on 2026-04-23:
// a hermes-runtime workspace whose Config tab showed "LangGraph
// (default)" in the runtime dropdown, an empty Model field, and a
// scary red "No config.yaml found" banner. Clicking Save would
// silently PATCH runtime back to LangGraph, breaking the workspace.
//
// Each test pins one invariant. If any fails, the bug is back.

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

afterEach(cleanup);

// ── API mock ──────────────────────────────────────────────────────────
// ConfigTab calls three endpoints on load:
//   1. GET /workspaces/:id            — workspace metadata (runtime)
//   2. GET /workspaces/:id/model      — model
//   3. GET /workspaces/:id/files/config.yaml — template-managed config (may 404)
// And POST /templates for the runtime dropdown options.
//
// Each test wires the mock to return the shape that matches the scenario
// it's pinning. Unhandled URLs default to rejecting so the test fails loud
// if ConfigTab queries something unexpected.
const apiGet = vi.fn();
const apiPatch = vi.fn();
const apiPut = vi.fn();
vi.mock("@/lib/api", () => ({
  api: {
    get: (path: string) => apiGet(path),
    patch: (path: string, body: unknown) => apiPatch(path, body),
    put: (path: string, body: unknown) => apiPut(path, body),
    post: vi.fn(),
    del: vi.fn(),
  },
}));

// Zustand store used by Save → restart. Not exercised in these tests.
vi.mock("@/store/canvas", () => ({
  useCanvasStore: Object.assign(
    (selector: (s: unknown) => unknown) => selector({ restartWorkspace: vi.fn(), updateNodeData: vi.fn() }),
    { getState: () => ({ restartWorkspace: vi.fn(), updateNodeData: vi.fn() }) },
  ),
}));

// AgentCardSection fetches its own data — stub to avoid noise.
vi.mock("../AgentCardSection", () => ({
  AgentCardSection: () => <div data-testid="agent-card-stub" />,
}));

import { ConfigTab } from "../ConfigTab";

// helper — wire the api.get mock for one scenario
function wireApi(opts: {
  workspaceRuntime?: string;
  workspaceModel?: string;
  configYamlContent?: string | null; // null = 404
  templates?: Array<{ id: string; name?: string; runtime?: string; models?: unknown[] }>;
}) {
  apiGet.mockImplementation((path: string) => {
    if (path === `/workspaces/ws-test`) {
      return Promise.resolve({ runtime: opts.workspaceRuntime ?? "" });
    }
    if (path === `/workspaces/ws-test/model`) {
      return Promise.resolve({ model: opts.workspaceModel ?? "" });
    }
    if (path === `/workspaces/ws-test/files/config.yaml`) {
      if (opts.configYamlContent === null) {
        return Promise.reject(new Error("not found"));
      }
      return Promise.resolve({ content: opts.configYamlContent ?? "" });
    }
    if (path === "/templates") {
      return Promise.resolve(opts.templates ?? []);
    }
    return Promise.reject(new Error(`unmocked api.get: ${path}`));
  });
}

beforeEach(() => {
  apiGet.mockReset();
  apiPatch.mockReset();
  apiPut.mockReset();
});

describe("ConfigTab — hermes workspace", () => {
  it("loads runtime from workspace metadata when config.yaml is missing (#1894 bug 1)", async () => {
    // This is the hermes case: no platform config.yaml, so the form must
    // fall back to GET /workspaces/:id's runtime field. Before the fix, the
    // runtime dropdown showed "LangGraph (default)" because the fallback
    // didn't exist.
    wireApi({
      workspaceRuntime: "hermes",
      workspaceModel: "openai/gpt-4o",
      configYamlContent: null,
      templates: [{ id: "t-hermes", name: "Hermes", runtime: "hermes", models: [] }],
    });

    render(<ConfigTab workspaceId="ws-test" />);

    // Wait for loads
    const select = await waitFor(() => screen.getByRole("combobox", { name: /runtime/i }));
    expect((select as HTMLSelectElement).value).toBe("hermes");
  });

  it("does NOT show 'No config.yaml found' error for hermes (#1894 bug 3)", async () => {
    // Hermes manages its own config at ~/.hermes/config.yaml on the
    // workspace host — the platform config.yaml NOT existing is expected,
    // not an error. Showing a red error banner misleads the user.
    wireApi({
      workspaceRuntime: "hermes",
      configYamlContent: null,
      templates: [{ id: "t-hermes", name: "Hermes", runtime: "hermes", models: [] }],
    });

    render(<ConfigTab workspaceId="ws-test" />);

    await waitFor(() => {
      const node = screen.queryByText(/No config\.yaml found/i);
      // Assert the red error is absent; a gray info banner with the same
      // phrase would also fail this (which is what we want — we don't
      // want any "no config.yaml" phrasing on hermes at all).
      expect(node).toBeNull();
    });
  });

  it("shows hermes-specific info banner pointing to Terminal tab (#1894)", async () => {
    wireApi({
      workspaceRuntime: "hermes",
      configYamlContent: null,
      templates: [{ id: "t-hermes", name: "Hermes", runtime: "hermes", models: [] }],
    });

    render(<ConfigTab workspaceId="ws-test" />);

    await waitFor(() => {
      expect(screen.getByText(/Hermes manages its own config/i)).toBeTruthy();
    });
  });

  it("DOES show 'No config.yaml found' error for langgraph workspace (default runtime)", async () => {
    // Regression guard the other way — the gray info banner is hermes-
    // specific. A langgraph workspace with no config.yaml SHOULD still
    // see the red error so the user knows to provide a template config.
    wireApi({
      workspaceRuntime: "",
      configYamlContent: null,
      templates: [],
    });

    render(<ConfigTab workspaceId="ws-test" />);

    await waitFor(() => {
      expect(screen.getByText(/No config\.yaml found/i)).toBeTruthy();
    });
  });
});

describe("ConfigTab — config.yaml on disk", () => {
  it("config.yaml runtime/model wins when present, workspace metadata is fallback", async () => {
    // If the workspace DB has runtime=langgraph but config.yaml declares
    // runtime: crewai, the form should show crewai (config.yaml wins).
    // Prevents silent runtime drift across reads.
    wireApi({
      workspaceRuntime: "langgraph", // DB
      configYamlContent: 'runtime: crewai\nmodel: "claude-opus"\n',
      templates: [
        { id: "t-crewai", name: "CrewAI", runtime: "crewai", models: [] },
      ],
    });

    render(<ConfigTab workspaceId="ws-test" />);

    const select = await waitFor(() => screen.getByRole("combobox", { name: /runtime/i }));
    expect((select as HTMLSelectElement).value).toBe("crewai");
  });
});
