import { render, screen, within } from "@testing-library/react";
import type { UpstreamHealth, UpstreamHealthEntry, UpstreamPeriodStats } from "@/lib/types";
import UpstreamHealthTable from "./UpstreamHealthTable";

const NOW_S = 1_700_000_000;

beforeEach(() => {
	vi.spyOn(Date, "now").mockReturnValue(NOW_S * 1000);
});

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

const ZERO: UpstreamPeriodStats = {
	attempts: 0,
	successes: 0,
	failures: 0,
	success_rate: null,
	last_failure_at: null,
	last_failure_error: null,
};

function period(overrides: Partial<UpstreamPeriodStats> = {}): UpstreamPeriodStats {
	return {
		attempts: 100,
		successes: 90,
		failures: 10,
		success_rate: 0.9,
		// 3h30m ago, far from a unit boundary.
		last_failure_at: NOW_S - 12_600,
		last_failure_error: "Timeout",
		...overrides,
	};
}

function entry(overrides: Partial<UpstreamHealthEntry> = {}): UpstreamHealthEntry {
	return {
		url: "https://dns.example/dns-query",
		enabled: true,
		available: true,
		period: period(),
		...overrides,
	};
}

function renderTable(upstreams: UpstreamHealthEntry[], overrides: Partial<UpstreamHealth> = {}) {
	const health: UpstreamHealth = {
		period: "24h",
		since: NOW_S - 86_400,
		until: NOW_S,
		available: upstreams.filter((upstream) => upstream.available).length,
		total: upstreams.length,
		complete: true,
		upstreams,
		...overrides,
	};
	render(<UpstreamHealthTable health={health} />);
}

function rowOf(url: string): HTMLElement {
	const cell = screen.getByText(url);
	const row = cell.closest("tr");
	if (row === null) throw new Error(`no row for ${url}`);
	return row;
}

test("the ranged columns sit under a header naming the selected period", () => {
	renderTable([entry()]);

	expect(screen.getByRole("columnheader", { name: "Selected period · 24h" })).toBeTruthy();
	for (const name of ["Upstream", "Status now", "Attempts", "Failures", "Success rate", "Last failure"]) {
		expect(screen.getByRole("columnheader", { name })).toBeTruthy();
	}

	// The unranged yes/no pair the ranged table replaced.
	expect(screen.queryByRole("columnheader", { name: "Enabled" })).toBeNull();
	expect(screen.queryByRole("columnheader", { name: "Available" })).toBeNull();
});

test("status now is one word from live state, not from the window", () => {
	renderTable([
		entry({ url: "https://a.example/dns-query" }),
		entry({ url: "https://b.example/dns-query", available: false }),
		entry({ url: "https://c.example/dns-query", enabled: false, available: false }),
	]);

	expect(within(rowOf("https://a.example/dns-query")).getByText("Available")).toBeTruthy();
	expect(within(rowOf("https://b.example/dns-query")).getByText("Backing off")).toBeTruthy();
	expect(within(rowOf("https://c.example/dns-query")).getByText("Disabled")).toBeTruthy();
});

test("last failure pairs the error name with its age, em-dash when the window holds none", () => {
	renderTable([
		entry({ url: "https://a.example/dns-query" }),
		entry({
			url: "https://b.example/dns-query",
			period: period({ last_failure_at: null, last_failure_error: null }),
		}),
	]);

	expect(within(rowOf("https://a.example/dns-query")).getByText("Timeout · 3h ago")).toBeTruthy();
	expect(within(rowOf("https://b.example/dns-query")).getByText("—")).toBeTruthy();
});

test("a window with no attempts renders em-dashes and never a perfect rate", () => {
	renderTable([entry({ period: ZERO })]);

	const cells = within(rowOf("https://dns.example/dns-query")).getAllByRole("cell");
	expect(cells.map((cell) => cell.textContent)).toEqual([
		"https://dns.example/dns-query",
		"Available",
		"0",
		"0",
		"—",
		"—",
	]);
	expect(screen.queryByText("100.0%")).toBeNull();
	expect(screen.queryByText("0.0%")).toBeNull();
});

test("the card says so when every upstream was idle in the window", () => {
	renderTable([entry({ url: "https://a.example/dns-query", period: ZERO }), entry({ period: ZERO })]);

	expect(screen.getByText("No upstream attempts in this period.")).toBeTruthy();
});

test("one upstream with attempts keeps the idle message away", () => {
	renderTable([entry({ url: "https://a.example/dns-query", period: ZERO }), entry()]);

	expect(screen.queryByText("No upstream attempts in this period.")).toBeNull();
});

test("an incomplete window carries a note; a complete one claims nothing", () => {
	renderTable([entry()], { complete: false });
	expect(screen.getByText(/history incomplete/i)).toBeTruthy();
});

test("a complete window shows no completeness text at all", () => {
	renderTable([entry()], { complete: true });

	expect(screen.queryByText(/history incomplete/i)).toBeNull();
	expect(screen.queryByText(/complete/i)).toBeNull();
});

test("an empty pool says so instead of drawing a table", () => {
	renderTable([]);

	expect(screen.getByText("No upstreams configured.")).toBeTruthy();
	expect(screen.queryByRole("table")).toBeNull();
});

test("a rate a hair under perfect never rounds up to 100.0% while failures stand", () => {
	// The real row that produced this: 12,698 attempts, 2 failures, 99.984%.
	renderTable([
		entry({
			period: period({ attempts: 12_698, successes: 12_696, failures: 2, success_rate: 12_696 / 12_698 }),
		}),
	]);

	expect(screen.queryByText("100.0%")).toBeNull();
	expect(screen.getByText("99.9%")).toBeTruthy();
});

test("a rate a hair above nothing never rounds down to 0.0% while successes stand", () => {
	renderTable([
		entry({
			period: period({ attempts: 12_698, successes: 2, failures: 12_696, success_rate: 2 / 12_698 }),
		}),
	]);

	expect(screen.queryByText("0.0%")).toBeNull();
	expect(screen.getByText("0.1%")).toBeTruthy();
});

test("a window with no failures at all still reads 100.0%", () => {
	renderTable([entry({ period: period({ attempts: 500, successes: 500, failures: 0, success_rate: 1 }) })]);

	expect(screen.getByText("100.0%")).toBeTruthy();
});
