import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor, fireEvent } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import type { ReactNode } from "react";
import UsagePage from "./page";

// ─── Component Prop Types ────────────────────────────────────────────

interface ChildrenProps {
  children: ReactNode;
}

interface StyledChildrenProps extends ChildrenProps {
  className?: string;
}

// ─── Mocks ───────────────────────────────────────────────────────────

// Mock @/components/ui/card — @mosaic/ui can't be resolved in vitest
vi.mock("@/components/ui/card", () => ({
  Card: ({ children, className }: StyledChildrenProps): React.JSX.Element => (
    <div className={className}>{children}</div>
  ),
  CardHeader: ({ children }: ChildrenProps): React.JSX.Element => <div>{children}</div>,
  CardContent: ({ children, className }: StyledChildrenProps): React.JSX.Element => (
    <div className={className}>{children}</div>
  ),
  CardFooter: ({ children }: ChildrenProps): React.JSX.Element => <div>{children}</div>,
  CardTitle: ({ children, className }: StyledChildrenProps): React.JSX.Element => (
    <h3 className={className}>{children}</h3>
  ),
  CardDescription: ({ children, className }: StyledChildrenProps): React.JSX.Element => (
    <p className={className}>{children}</p>
  ),
}));

// Mock recharts — jsdom has no SVG layout engine, so we render stubs
vi.mock("recharts", () => ({
  LineChart: ({ children }: ChildrenProps): React.JSX.Element => (
    <div data-testid="recharts-line-chart">{children}</div>
  ),
  Line: (): React.JSX.Element => <div />,
  BarChart: ({ children }: ChildrenProps): React.JSX.Element => (
    <div data-testid="recharts-bar-chart">{children}</div>
  ),
  Bar: (): React.JSX.Element => <div />,
  PieChart: ({ children }: ChildrenProps): React.JSX.Element => (
    <div data-testid="recharts-pie-chart">{children}</div>
  ),
  Pie: (): React.JSX.Element => <div />,
  Cell: (): React.JSX.Element => <div />,
  XAxis: (): React.JSX.Element => <div />,
  YAxis: (): React.JSX.Element => <div />,
  CartesianGrid: (): React.JSX.Element => <div />,
  Tooltip: (): React.JSX.Element => <div />,
  ResponsiveContainer: ({ children }: ChildrenProps): React.JSX.Element => <div>{children}</div>,
  Legend: (): React.JSX.Element => <div />,
}));

// Mock the telemetry API module
vi.mock("@/lib/api/telemetry", () => ({
  fetchUsageSummary: vi.fn(),
  fetchTokenUsage: vi.fn(),
  fetchCostBreakdown: vi.fn(),
  fetchTaskOutcomes: vi.fn(),
}));

// Import mocked modules after vi.mock
import {
  fetchUsageSummary,
  fetchTokenUsage,
  fetchCostBreakdown,
  fetchTaskOutcomes,
} from "@/lib/api/telemetry";

// ─── Test Data ───────────────────────────────────────────────────────

const mockSummary = {
  totalTokens: 245800,
  totalCost: 3.42,
  taskCount: 47,
  avgQualityGatePassRate: 0.87,
};

const mockTokenUsage = [
  { date: "2026-02-08", inputTokens: 10000, outputTokens: 5000, totalTokens: 15000 },
  { date: "2026-02-09", inputTokens: 12000, outputTokens: 6000, totalTokens: 18000 },
];

const mockCostBreakdown = [
  { model: "claude-sonnet-4-5", provider: "anthropic", cost: 18.5, taskCount: 124 },
  { model: "gpt-4o", provider: "openai", cost: 12.3, taskCount: 89 },
];

const mockTaskOutcomes = [
  { outcome: "Success", count: 312, color: "#6EBF8B" },
  { outcome: "Partial", count: 48, color: "#F5C862" },
];

function setupMocks(overrides?: { empty?: boolean; error?: boolean }): void {
  if (overrides?.error) {
    vi.mocked(fetchUsageSummary).mockRejectedValue(new Error("Network error"));
    vi.mocked(fetchTokenUsage).mockRejectedValue(new Error("Network error"));
    vi.mocked(fetchCostBreakdown).mockRejectedValue(new Error("Network error"));
    vi.mocked(fetchTaskOutcomes).mockRejectedValue(new Error("Network error"));
    return;
  }

  const summary = overrides?.empty ? { ...mockSummary, taskCount: 0 } : mockSummary;

  vi.mocked(fetchUsageSummary).mockResolvedValue(summary);
  vi.mocked(fetchTokenUsage).mockResolvedValue(mockTokenUsage);
  vi.mocked(fetchCostBreakdown).mockResolvedValue(mockCostBreakdown);
  vi.mocked(fetchTaskOutcomes).mockResolvedValue(mockTaskOutcomes);
}

function setupPendingMocks(): void {
  // eslint-disable-next-line @typescript-eslint/no-empty-function -- intentionally unresolved for loading-state test
  const pending = new Promise<never>(() => {});
  vi.mocked(fetchUsageSummary).mockReturnValue(pending);
  vi.mocked(fetchTokenUsage).mockReturnValue(pending);
  vi.mocked(fetchCostBreakdown).mockReturnValue(pending);
  vi.mocked(fetchTaskOutcomes).mockReturnValue(pending);
}

// ─── Tests ───────────────────────────────────────────────────────────

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

  it("should render the page title and subtitle", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });

    expect(screen.getByRole("heading", { level: 1 })).toHaveTextContent("Usage");
    expect(screen.getByText("Token usage and cost overview")).toBeInTheDocument();
  });

  it("should have proper layout structure", async (): Promise<void> => {
    setupMocks();
    const { container } = render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });

    const main = container.querySelector("main");
    expect(main).toBeInTheDocument();
  });

  it("should show loading skeleton initially", (): void => {
    setupPendingMocks();
    render(<UsagePage />);
    expect(screen.getByTestId("loading-skeleton")).toBeInTheDocument();
  });

  it("should render summary cards after loading", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });

    // Check summary card values
    expect(screen.getByText("Total Tokens")).toBeInTheDocument();
    expect(screen.getByText("245.8K")).toBeInTheDocument();
    expect(screen.getByText("Estimated Cost")).toBeInTheDocument();
    expect(screen.getByText("$3.42")).toBeInTheDocument();
    expect(screen.getByText("Task Count")).toBeInTheDocument();
    expect(screen.getByText("47")).toBeInTheDocument();
    expect(screen.getByText("Quality Gate Pass Rate")).toBeInTheDocument();
    expect(screen.getByText("87.0%")).toBeInTheDocument();
  });

  it("should render all chart sections after loading", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("token-usage-chart")).toBeInTheDocument();
      expect(screen.getByTestId("cost-breakdown-chart")).toBeInTheDocument();
      expect(screen.getByTestId("task-outcomes-chart")).toBeInTheDocument();
    });
  });

  it("should render the time range selector with three options", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });

    expect(screen.getByText("7 Days")).toBeInTheDocument();
    expect(screen.getByText("30 Days")).toBeInTheDocument();
    expect(screen.getByText("90 Days")).toBeInTheDocument();
  });

  it("should have 30 Days selected by default", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });

    const button30d = screen.getByText("30 Days");
    expect(button30d).toHaveAttribute("aria-pressed", "true");
  });

  it("should change time range when a different option is clicked", async (): Promise<void> => {
    setupMocks();
    const user = userEvent.setup();
    render(<UsagePage />);

    // Wait for initial load
    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });

    // Click 7 Days
    const button7d = screen.getByText("7 Days");
    await user.click(button7d);

    await waitFor((): void => {
      expect(fetchUsageSummary).toHaveBeenCalledWith("7d");
    });

    expect(button7d).toHaveAttribute("aria-pressed", "true");
    expect(screen.getByText("30 Days")).toHaveAttribute("aria-pressed", "false");
  });

  it("should refetch data when time range changes", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    // Wait for initial load (30d default)
    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });

    // Initial call was with "30d"
    expect(fetchUsageSummary).toHaveBeenCalledWith("30d");

    // Change to 7d
    fireEvent.click(screen.getByText("7 Days"));

    await waitFor((): void => {
      expect(fetchUsageSummary).toHaveBeenCalledWith("7d");
    });
  });

  it("should show empty state when no tasks exist", async (): Promise<void> => {
    setupMocks({ empty: true });
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("empty-state")).toBeInTheDocument();
    });

    expect(screen.getByText("No usage data yet")).toBeInTheDocument();
  });

  it("should show error state on fetch failure", async (): Promise<void> => {
    setupMocks({ error: true });
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByText("Network error")).toBeInTheDocument();
    });

    expect(screen.getByText("Try again")).toBeInTheDocument();
  });

  it("should retry loading when Try again button is clicked after error", async (): Promise<void> => {
    setupMocks({ error: true });
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByText("Try again")).toBeInTheDocument();
    });

    // Now set up success mocks and click retry
    setupMocks();
    fireEvent.click(screen.getByText("Try again"));

    await waitFor((): void => {
      expect(screen.getByTestId("summary-cards")).toBeInTheDocument();
    });
  });

  it("should display chart section titles", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByText("Token Usage Over Time")).toBeInTheDocument();
      expect(screen.getByText("Cost by Model")).toBeInTheDocument();
      expect(screen.getByText("Task Outcomes")).toBeInTheDocument();
    });
  });

  it("should render recharts components within chart containers", async (): Promise<void> => {
    setupMocks();
    render(<UsagePage />);

    await waitFor((): void => {
      expect(screen.getByTestId("recharts-line-chart")).toBeInTheDocument();
      expect(screen.getByTestId("recharts-bar-chart")).toBeInTheDocument();
      expect(screen.getByTestId("recharts-pie-chart")).toBeInTheDocument();
    });
  });
});
