/**
 * TaskProgressWidget Component Tests
 */

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

const mockFetch = vi.fn();
global.fetch = mockFetch as unknown as typeof fetch;

describe("TaskProgressWidget", (): void => {
  beforeEach((): void => {
    vi.clearAllMocks();
  });

  afterEach((): void => {
    cleanup();
  });

  it("should render loading state initially", (): void => {
    mockFetch.mockImplementation(
      () =>
        new Promise(() => {
          // Never-resolving promise for loading state
        })
    );

    render(<TaskProgressWidget id="task-progress-1" />);

    expect(screen.getByText(/loading task progress/i)).toBeInTheDocument();
  });

  it("should display tasks after successful fetch", async (): Promise<void> => {
    const mockTasks = [
      {
        agentId: "agent-1",
        taskId: "TASK-001",
        status: "running",
        agentType: "worker",
        spawnedAt: new Date().toISOString(),
      },
      {
        agentId: "agent-2",
        taskId: "TASK-002",
        status: "completed",
        agentType: "reviewer",
        spawnedAt: new Date(Date.now() - 3600000).toISOString(),
        completedAt: new Date().toISOString(),
      },
    ];

    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve(mockTasks),
    } as unknown as Response);

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText("TASK-001")).toBeInTheDocument();
      expect(screen.getByText("TASK-002")).toBeInTheDocument();
    });

    // Check stats
    expect(screen.getByText("2")).toBeInTheDocument(); // Total
  });

  it("should display error state when fetch fails", async (): Promise<void> => {
    mockFetch.mockRejectedValueOnce(new Error("Network error"));

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText(/unable to reach orchestrator/i)).toBeInTheDocument();
    });
  });

  it("should display empty state when no tasks", async (): Promise<void> => {
    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve([]),
    } as unknown as Response);

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText(/no agent tasks in progress/i)).toBeInTheDocument();
    });
  });

  it("should show agent type badges", async (): Promise<void> => {
    const mockTasks = [
      {
        agentId: "agent-1",
        taskId: "TASK-001",
        status: "running",
        agentType: "worker",
        spawnedAt: new Date().toISOString(),
      },
      {
        agentId: "agent-2",
        taskId: "TASK-002",
        status: "running",
        agentType: "reviewer",
        spawnedAt: new Date().toISOString(),
      },
      {
        agentId: "agent-3",
        taskId: "TASK-003",
        status: "running",
        agentType: "tester",
        spawnedAt: new Date().toISOString(),
      },
    ];

    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve(mockTasks),
    } as unknown as Response);

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText("Worker")).toBeInTheDocument();
      expect(screen.getByText("Reviewer")).toBeInTheDocument();
      expect(screen.getByText("Tester")).toBeInTheDocument();
    });
  });

  it("should display error message for failed tasks", async (): Promise<void> => {
    const mockTasks = [
      {
        agentId: "agent-1",
        taskId: "TASK-FAIL",
        status: "failed",
        agentType: "worker",
        spawnedAt: new Date().toISOString(),
        error: "Build failed: type errors",
      },
    ];

    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve(mockTasks),
    } as unknown as Response);

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText("Build failed: type errors")).toBeInTheDocument();
    });
  });

  it("should display error for non-ok HTTP responses", async (): Promise<void> => {
    mockFetch.mockResolvedValueOnce({
      ok: false,
      status: 500,
    } as unknown as Response);

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText(/unable to reach orchestrator/i)).toBeInTheDocument();
    });
  });

  it("should clear interval on unmount", async (): Promise<void> => {
    const clearIntervalSpy = vi.spyOn(global, "clearInterval");

    mockFetch.mockResolvedValue({
      ok: true,
      json: () => Promise.resolve([]),
    } as unknown as Response);

    const { unmount } = render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText(/no agent tasks in progress/i)).toBeInTheDocument();
    });

    unmount();

    expect(clearIntervalSpy).toHaveBeenCalled();
    clearIntervalSpy.mockRestore();
  });

  it("should limit displayed tasks to 10", async (): Promise<void> => {
    const mockTasks = Array.from({ length: 15 }, (_, i) => ({
      agentId: `agent-${String(i)}`,
      taskId: `SLICE-${String(i).padStart(3, "0")}`,
      status: "running",
      agentType: "worker",
      spawnedAt: new Date().toISOString(),
    }));

    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve(mockTasks),
    } as unknown as Response);

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      // Only 10 task cards should be rendered despite 15 tasks
      const workerBadges = screen.getAllByText("Worker");
      expect(workerBadges).toHaveLength(10);
    });
  });

  it("should sort active tasks before completed ones", async (): Promise<void> => {
    const mockTasks = [
      {
        agentId: "agent-completed",
        taskId: "COMPLETED-TASK",
        status: "completed",
        agentType: "worker",
        spawnedAt: new Date(Date.now() - 7200000).toISOString(),
        completedAt: new Date().toISOString(),
      },
      {
        agentId: "agent-running",
        taskId: "RUNNING-TASK",
        status: "running",
        agentType: "worker",
        spawnedAt: new Date().toISOString(),
      },
    ];

    mockFetch.mockResolvedValueOnce({
      ok: true,
      json: () => Promise.resolve(mockTasks),
    } as unknown as Response);

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      const taskElements = screen.getAllByText(/TASK/);
      expect(taskElements).toHaveLength(2);
      // Running task should appear before completed
      expect(taskElements[0]?.textContent).toBe("RUNNING-TASK");
      expect(taskElements[1]?.textContent).toBe("COMPLETED-TASK");
    });
  });

  it("should display latest orchestrator event when available", async (): Promise<void> => {
    mockFetch.mockImplementation((input: RequestInfo | URL) => {
      let url = "";
      if (typeof input === "string") {
        url = input;
      } else if (input instanceof URL) {
        url = input.toString();
      } else {
        url = input.url;
      }
      if (url.includes("/api/orchestrator/agents")) {
        return Promise.resolve({
          ok: true,
          json: () => Promise.resolve([]),
        } as unknown as Response);
      }
      if (url.includes("/api/orchestrator/queue/stats")) {
        return Promise.resolve({
          ok: true,
          json: () =>
            Promise.resolve({
              pending: 0,
              active: 0,
              completed: 0,
              failed: 0,
              delayed: 0,
            }),
        } as unknown as Response);
      }
      if (url.includes("/api/orchestrator/events/recent")) {
        return Promise.resolve({
          ok: true,
          json: () =>
            Promise.resolve({
              events: [
                {
                  type: "task.executing",
                  timestamp: new Date().toISOString(),
                  taskId: "TASK-123",
                },
              ],
            }),
        } as unknown as Response);
      }
      return Promise.reject(new Error("Unknown endpoint"));
    });

    render(<TaskProgressWidget id="task-progress-1" />);

    await waitFor(() => {
      expect(screen.getByText(/Latest: task.executing · TASK-123/i)).toBeInTheDocument();
    });
  });
});
