// WorkloadTrendChart: lazy fetch lifecycle (loading → error + retry →
// empty → chart) against a mocked api-client, plus the pure usageChartData
// mapper. No recharts layout assertions — jsdom renders zero-size
// containers (RegionTrendChart's chart is untested for the same reason).

import { afterEach, describe, expect, it, vi } from "vitest";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { WorkloadTrendChart, usageChartData } from "@/components/regions/WorkloadTrendChart";
import { fetchWorkloadUsage } from "@/lib/api-client";
import type { UsagePoint, UsageSeriesResponse } from "@/lib/api-types";

vi.mock("@/lib/api-client", () => ({
  fetchWorkloadUsage: vi.fn(),
}));

const mockedFetch = vi.mocked(fetchWorkloadUsage);

afterEach(() => {
  vi.clearAllMocks();
});

const props = {
  regionKey: "aws-us-west-2",
  ns: "apps",
  kind: "Deployment",
  name: "web",
};

function point(t: number, min: number, avg: number, max: number): UsagePoint {
  return { t, minCpu: min, avgCpu: avg, maxCpu: max, minMem: min, avgMem: avg, maxMem: max };
}

describe("usageChartData", () => {
  it("maps engine points to chart rows with UTC+8 tick labels", () => {
    const t0 = Date.UTC(2026, 8, 5, 0, 0) / 1000; // 2026-09-05 00:00 UTC -> 08:00 Beijing
    const rows = usageChartData([point(t0, 0.5, 1, 2), point(t0 + 7200, 0.75, 1.5, 2.5)]);
    expect(rows).toEqual([
      { tick: "09-05 08:00", minCpu: 0.5, avgCpu: 1, maxCpu: 2, minMem: 0.5, avgMem: 1, maxMem: 2 },
      { tick: "09-05 10:00", minCpu: 0.75, avgCpu: 1.5, maxCpu: 2.5, minMem: 0.75, avgMem: 1.5, maxMem: 2.5 },
    ]);
  });
});

describe("WorkloadTrendChart", () => {
  it("fetches on mount at the default window and shows the loading state", async () => {
    mockedFetch.mockReturnValue(new Promise(() => {}) as never);
    render(<WorkloadTrendChart {...props} />);
    expect(mockedFetch).toHaveBeenCalledWith(
      props.regionKey,
      {
        ns: props.ns,
        kind: props.kind,
        name: props.name,
      },
      "24h",
    );
    expect(screen.getByText("loading usage trend…")).toBeTruthy();
  });

  it("refetches when the window preset changes", async () => {
    mockedFetch.mockReturnValue(new Promise(() => {}) as never);
    render(<WorkloadTrendChart {...props} />);
    fireEvent.change(screen.getByRole("combobox", { name: "trend time range" }), {
      target: { value: "7d" },
    });
    expect(mockedFetch).toHaveBeenCalledWith(
      props.regionKey,
      { ns: props.ns, kind: props.kind, name: props.name },
      "7d",
    );
    expect(screen.getByText("loading usage trend…")).toBeTruthy();
  });

  it("shows an inline error with retry, and retry refetches", async () => {
    mockedFetch
      .mockRejectedValueOnce(new Error("dataplane down") as never)
      .mockResolvedValueOnce({ mode: "ratio", points: [point(1, 0.05, 0.1, 0.2), point(2, 0.05, 0.1, 0.2)] } as never);
    render(<WorkloadTrendChart {...props} />);
    await waitFor(() => {
      expect(screen.getByText(/dataplane down/)).toBeTruthy();
    });
    fireEvent.click(screen.getByRole("button", { name: "retry" }));
    expect(mockedFetch).toHaveBeenCalledTimes(2);
    await waitFor(() => {
      expect(screen.queryByText(/dataplane down/)).toBeNull();
    });
  });

  it("renders the empty state for fewer than two samples", async () => {
    mockedFetch.mockResolvedValue({ mode: "absolute", points: [point(1, 0.5, 1, 2)] } as UsageSeriesResponse);
    render(<WorkloadTrendChart {...props} />);
    await waitFor(() => {
      expect(screen.getByText("Need at least two samples for a trend.")).toBeTruthy();
    });
  });

  it("renders the chart for a full series without erroring", async () => {
    const series: UsageSeriesResponse = {
      mode: "absolute",
      points: [
        point(100, 0.4, 1, 2),
        point(200, 0.9, 1.5, 2.5),
        point(300, 0.5, 0.8, 2.1),
      ],
    };
    mockedFetch.mockResolvedValue(series);
    const { container } = render(<WorkloadTrendChart {...props} />);
    // jsdom gives ResponsiveContainer zero size, so the inner .recharts-wrapper
    // never mounts — the responsive container itself is the chart witness.
    await waitFor(() => {
      expect(container.querySelector(".recharts-responsive-container")).toBeTruthy();
    });
    expect(screen.queryByText(/unavailable/i)).toBeNull();
  });
});
