/**
 * Tests for useOrchestratorCommands hook
 */

import { describe, it, expect, vi, beforeEach } from "vitest";
import { renderHook, act } from "@testing-library/react";
import { useOrchestratorCommands } from "./useOrchestratorCommands";
import type { Message } from "./useChat";

// Mock fetch globally
const mockFetch = vi.fn();
global.fetch = mockFetch;

function makeOkResponse(data: unknown): Response {
  return {
    ok: true,
    status: 200,
    json: () => Promise.resolve(data),
    text: () => Promise.resolve(JSON.stringify(data)),
  } as unknown as Response;
}

/** Run executeCommand and return the result synchronously after act() */
async function runCommand(
  executeCommand: (content: string) => Promise<Message | null>,
  content: string
): Promise<Message | null> {
  let msg: Message | null = null;
  await act(async () => {
    msg = await executeCommand(content);
  });
  return msg;
}

describe("useOrchestratorCommands", () => {
  beforeEach(() => {
    mockFetch.mockReset();
  });

  describe("isCommand", () => {
    it("returns true for messages starting with /", () => {
      const { result } = renderHook(() => useOrchestratorCommands());
      expect(result.current.isCommand("/status")).toBe(true);
      expect(result.current.isCommand("/agents")).toBe(true);
      expect(result.current.isCommand("/help")).toBe(true);
      expect(result.current.isCommand("  /status")).toBe(true);
    });

    it("returns false for regular messages", () => {
      const { result } = renderHook(() => useOrchestratorCommands());
      expect(result.current.isCommand("hello")).toBe(false);
      expect(result.current.isCommand("tell me about /status")).toBe(false);
      expect(result.current.isCommand("")).toBe(false);
    });
  });

  describe("executeCommand", () => {
    describe("/help", () => {
      it("returns help message without network calls", async () => {
        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/help");

        expect(mockFetch).not.toHaveBeenCalled();
        expect(msg).not.toBeNull();
        expect(msg?.role).toBe("assistant");
        expect(msg?.content).toContain("/status");
        expect(msg?.content).toContain("/agents");
        expect(msg?.content).toContain("/jobs");
        expect(msg?.content).toContain("/pause");
        expect(msg?.content).toContain("/resume");
      });

      it("returns message with id and createdAt", async () => {
        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/help");

        expect(msg?.id).toBeDefined();
        expect(msg?.createdAt).toBeDefined();
      });
    });

    describe("/status", () => {
      it("calls /api/orchestrator/health and returns formatted status", async () => {
        mockFetch.mockResolvedValueOnce(
          makeOkResponse({ status: "ready", version: "1.2.3", uptime: 3661 })
        );

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/status");

        expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/health", { method: "GET" });
        expect(msg?.role).toBe("assistant");
        expect(msg?.content).toContain("Ready");
        expect(msg?.content).toContain("1.2.3");
        expect(msg?.content).toContain("1h");
      });

      it("shows Not Ready when status is not ready", async () => {
        mockFetch.mockResolvedValueOnce(makeOkResponse({ status: "not-ready" }));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/status");

        expect(msg?.content).toContain("Not Ready");
      });

      it("handles network error gracefully", async () => {
        mockFetch.mockRejectedValueOnce(new Error("Connection refused"));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/status");

        expect(msg?.role).toBe("assistant");
        expect(msg?.content).toContain("Error");
        expect(msg?.content).toContain("Connection refused");
      });

      it("shows error from API response", async () => {
        mockFetch.mockResolvedValueOnce(
          makeOkResponse({ error: "ORCHESTRATOR_API_KEY is not configured" })
        );

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/status");

        expect(msg?.content).toContain("Not reachable");
      });
    });

    describe("/agents", () => {
      it("calls /api/orchestrator/agents and returns agent table", async () => {
        const agents = [
          { id: "agent-1", status: "active", type: "codex", startedAt: "2026-02-25T10:00:00Z" },
          {
            id: "agent-2",
            agentStatus: "TERMINATED",
            channel: "claude",
            startedAt: "2026-02-25T09:00:00Z",
          },
        ];
        mockFetch.mockResolvedValueOnce(makeOkResponse(agents));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/agents");

        expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/agents", { method: "GET" });
        expect(msg?.content).toContain("agent-1");
        expect(msg?.content).toContain("agent-2");
        expect(msg?.content).toContain("TERMINATED");
      });

      it("handles empty agent list", async () => {
        mockFetch.mockResolvedValueOnce(makeOkResponse([]));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/agents");

        expect(msg?.content).toContain("No agents currently running");
      });

      it("handles agents in nested object", async () => {
        const data = {
          agents: [{ id: "agent-nested", status: "active" }],
        };
        mockFetch.mockResolvedValueOnce(makeOkResponse(data));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/agents");

        expect(msg?.content).toContain("agent-nested");
      });

      it("handles network error gracefully", async () => {
        mockFetch.mockRejectedValueOnce(new Error("Timeout"));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/agents");

        expect(msg?.content).toContain("Error");
        expect(msg?.content).toContain("Timeout");
      });
    });

    describe("/jobs", () => {
      it("calls /api/orchestrator/queue/stats", async () => {
        mockFetch.mockResolvedValueOnce(
          makeOkResponse({ pending: 3, active: 1, completed: 42, failed: 0 })
        );

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/jobs");

        expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/stats", { method: "GET" });
        expect(msg?.content).toContain("3");
        expect(msg?.content).toContain("42");
        expect(msg?.content).toContain("Pending");
        expect(msg?.content).toContain("Completed");
      });

      it("/queue is an alias for /jobs", async () => {
        mockFetch.mockResolvedValueOnce(makeOkResponse({ pending: 0, active: 0 }));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/queue");

        expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/stats", { method: "GET" });
        expect(msg?.role).toBe("assistant");
      });

      it("shows paused indicator when queue is paused", async () => {
        mockFetch.mockResolvedValueOnce(makeOkResponse({ pending: 0, active: 0, paused: true }));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/jobs");

        expect(msg?.content).toContain("paused");
      });
    });

    describe("/pause", () => {
      it("calls POST /api/orchestrator/queue/pause", async () => {
        mockFetch.mockResolvedValueOnce(
          makeOkResponse({ success: true, message: "Queue paused." })
        );

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/pause");

        expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/pause", {
          method: "POST",
        });
        expect(msg?.content).toContain("paused");
      });

      it("handles API error response", async () => {
        mockFetch.mockResolvedValueOnce(makeOkResponse({ error: "Already paused." }));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/pause");

        expect(msg?.content).toContain("failed");
        expect(msg?.content).toContain("Already paused");
      });

      it("handles network error", async () => {
        mockFetch.mockRejectedValueOnce(new Error("Network failure"));

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/pause");

        expect(msg?.content).toContain("Error");
      });
    });

    describe("/resume", () => {
      it("calls POST /api/orchestrator/queue/resume", async () => {
        mockFetch.mockResolvedValueOnce(
          makeOkResponse({ success: true, message: "Queue resumed." })
        );

        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/resume");

        expect(mockFetch).toHaveBeenCalledWith("/api/orchestrator/queue/resume", {
          method: "POST",
        });
        expect(msg?.content).toContain("resumed");
      });
    });

    describe("unknown command", () => {
      it("returns help hint for unknown commands", async () => {
        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "/unknown-command");

        expect(mockFetch).not.toHaveBeenCalled();
        expect(msg?.content).toContain("Unknown command");
        expect(msg?.content).toContain("/unknown-command");
        expect(msg?.content).toContain("/help");
      });
    });

    describe("non-command input", () => {
      it("returns null for regular messages", async () => {
        const { result } = renderHook(() => useOrchestratorCommands());
        const msg = await runCommand(result.current.executeCommand, "hello world");

        expect(msg).toBeNull();
        expect(mockFetch).not.toHaveBeenCalled();
      });
    });
  });
});
