import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Settings } from "../Settings";
import type { MonitoringMachine } from "../../types";

const saveMachineMutate = vi.fn().mockResolvedValue({});
const deleteMachineMutate = vi.fn();
const testSSHMutate = vi
	.fn()
	.mockResolvedValue({
		message: "SSH auth succeeded",
		known_hosts_updated: true,
	});

let machines: MonitoringMachine[] = [];

vi.mock("../../hooks/useSettings", () => ({
	useMonitoringSettings: () => ({ data: machines }),
	useSSHKeys: () => ({ data: [] }),
	useSaveMonitoringMachine: () => ({
		mutateAsync: saveMachineMutate,
		isPending: false,
	}),
	useDeleteMonitoringMachine: () => ({ mutate: deleteMachineMutate }),
	useTestMonitoringMachineSSH: () => ({
		mutateAsync: testSSHMutate,
		isPending: false,
	}),
	useResetLocalDatabase: () => ({}),
	useSaveSSHKey: () => ({ mutateAsync: vi.fn() }),
	useGenerateSSHKey: () => ({ mutateAsync: vi.fn(), isPending: false }),
	useDeleteSSHKey: () => ({ mutate: vi.fn() }),
}));

function localMachine(
	overrides: Partial<MonitoringMachine> = {},
): MonitoringMachine {
	return {
		id: "m1",
		name: "This machine",
		mode: "local",
		enabled: true,
		services: ["monitoring", "files", "jellyfin"],
		host: "",
		port: 22,
		username: "",
		key_directory: "",
		key_name: "",
		ssh_key_id: "",
		ssh_private_key_set: false,
		ssh_private_key_passphrase_set: false,
		password_set: false,
		notes: "Primary node",
		...overrides,
	} as MonitoringMachine;
}

beforeEach(() => {
	saveMachineMutate.mockClear();
	deleteMachineMutate.mockClear();
	testSSHMutate.mockClear();
	machines = [];
});

describe("Settings", () => {
	it("renders the machine list from the mocked store", () => {
		machines = [localMachine()];
		render(<Settings />);
		// The rail row caption (mode · enabled) is unique to the selection rail.
		expect(screen.getByText("local · Enabled")).toBeInTheDocument();
	});

	it("saves a machine via the editor dialog (controlled useState parity)", async () => {
		machines = [localMachine()];
		render(<Settings />);

		// The detail-pane "Edit" has visible text "Edit"; the rail hover edit
		// affordance is icon-only (aria-label "Edit") — disambiguate by text.
		const detailEdit = screen
			.getAllByRole("button", { name: "Edit" })
			.find((button) => button.textContent === "Edit") as HTMLButtonElement;
		await userEvent.click(detailEdit);

		expect(screen.getByText("Edit machine")).toBeInTheDocument();

		// Rename through the labeled field, then save.
		const nameInput = screen.getByLabelText("Name");
		await userEvent.clear(nameInput);
		await userEvent.type(nameInput, "Worker node");

		await userEvent.click(screen.getByRole("button", { name: "Save machine" }));

		expect(saveMachineMutate).toHaveBeenCalledTimes(1);
		const saved = saveMachineMutate.mock.calls[0][0];
		expect(saved.name).toBe("Worker node");
		expect(saved.mode).toBe("local");
	});

	it("deletes a machine through the confirm dialog", async () => {
		machines = [localMachine()];
		render(<Settings />);

		// Detail-pane "Delete" opens the confirm dialog.
		await userEvent.click(screen.getByRole("button", { name: "Delete" }));
		expect(screen.getByText("Delete machine?")).toBeInTheDocument();

		// Confirm (the confirm dialog's "Delete" is the last one rendered).
		const deletes = screen.getAllByRole("button", { name: "Delete" });
		await userEvent.click(deletes[deletes.length - 1]);

		expect(deleteMachineMutate).toHaveBeenCalledTimes(1);
		expect(deleteMachineMutate).toHaveBeenCalledWith("m1");
	});
});
