/**
 * Regression test for the service-editor state-reset bug.
 *
 * ServiceConfigEditor initializes its editable state (name, config, secrets)
 * from the `instance` prop via useState. The parent must remount it (via
 * `key={instance.id}`) when the selected service changes — otherwise switching
 * services in the rail keeps the PREVIOUS service's name/config in state while
 * `instance.id`/`service_type` (read live from props) now point at the new one.
 * A save then wrote the stale name/config onto the new service's row
 * (e.g. saving qBittorrent renamed it "Jellyfin" with Jellyfin's URL).
 */

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

const saveServiceMutate = vi.fn().mockResolvedValue({});

// Settings invokes the machine/SSH hooks at the top level regardless of active
// tab, so stub them out to keep the render focused on the Services editor.
vi.mock("../../hooks/useSettings", () => ({
	useMonitoringSettings: () => ({ data: [] }),
	useSSHKeys: () => ({ data: [] }),
	useSaveMonitoringMachine: () => ({ mutateAsync: vi.fn(), isPending: false }),
	useDeleteMonitoringMachine: () => ({ mutate: vi.fn() }),
	useTestMonitoringMachineSSH: () => ({
		mutateAsync: vi.fn(),
		isPending: false,
	}),
	useResetLocalDatabase: () => ({}),
	useSaveSSHKey: () => ({ mutateAsync: vi.fn() }),
	useGenerateSSHKey: () => ({ mutateAsync: vi.fn(), isPending: false }),
	useDeleteSSHKey: () => ({ mutate: vi.fn() }),
}));

vi.mock("../../hooks/useDashboards", () => ({
	useDashboards: () => ({ data: [] }),
	useSaveDashboard: () => ({ mutate: vi.fn(), isPending: false }),
	useDeleteDashboard: () => ({ mutate: vi.fn(), isPending: false }),
}));

vi.mock("../../hooks/useServices", () => ({
	useServiceTypes: () => ({
		data: [
			{
				service_type: "jellyfin",
				name: "Jellyfin",
				description: "Media server",
				config_schema: {
					type: "object",
					properties: {
						base_url: { type: "string", description: "URL" },
						timeout_seconds: { type: "integer" },
					},
				},
				secret_fields: [{ key: "api_key", label: "API key", required: true }],
				widget_kinds: [],
			},
			{
				service_type: "qbittorrent",
				name: "qBittorrent",
				description: "Torrent client",
				config_schema: {
					type: "object",
					properties: {
						base_url: { type: "string" },
						timeout_seconds: { type: "integer" },
					},
				},
				secret_fields: [
					{ key: "username", label: "Username", required: true },
					{ key: "password", label: "Password", required: true },
				],
				widget_kinds: [],
			},
		],
	}),
	useServiceInstances: () => ({
		data: [
			{
				id: "svc-jf",
				service_type: "jellyfin",
				name: "My Jellyfin",
				config: { base_url: "https://jf.example.com", timeout_seconds: 60 },
				secrets_set: { api_key: true },
				enabled: true,
				created_at: 0,
				updated_at: 0,
			},
			{
				id: "svc-qb",
				service_type: "qbittorrent",
				name: "My qBittorrent",
				config: { base_url: "https://qb.example.com", timeout_seconds: 60 },
				secrets_set: { username: true, password: true },
				enabled: true,
				created_at: 0,
				updated_at: 0,
			},
		],
	}),
	useSaveServiceInstance: () => ({
		mutateAsync: saveServiceMutate,
		isPending: false,
	}),
	useDeleteServiceInstance: () => ({ mutate: vi.fn() }),
	useTestServiceInstance: () => ({
		mutateAsync: vi.fn().mockResolvedValue({ ok: true }),
		isPending: false,
	}),
}));

beforeEach(() => {
	saveServiceMutate.mockClear();
});

describe("Settings > Services editor", () => {
	it("resets editable state when switching services so save does not clobber (regression)", async () => {
		render(
			<MemoryRouter initialEntries={["/settings?tab=services"]}>
				<Settings />
			</MemoryRouter>,
		);

		// Default selection is the first service (Jellyfin). Switch to qBittorrent
		// via the selection rail.
		await userEvent.click(screen.getByText("My qBittorrent"));

		// Save is gated on a passed test or "Save anyway"; toggle Save anyway so
		// we can save without exercising the (mocked) test mutation.
		await userEvent.click(screen.getByLabelText(/save anyway/i));

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

		expect(saveServiceMutate).toHaveBeenCalledTimes(1);
		const saved = saveServiceMutate.mock.calls[0][0];
		// Must target qBittorrent's id AND carry qBittorrent's own name/config —
		// not the stale Jellyfin values from the previously selected service.
		expect(saved.id).toBe("svc-qb");
		expect(saved.service_type).toBe("qbittorrent");
		expect(saved.name).toBe("My qBittorrent");
		expect(saved.config).toEqual({
			base_url: "https://qb.example.com",
			timeout_seconds: 60,
		});
	});

	it("offers service creation and dashboard management as Services subtabs", async () => {
		render(
			<MemoryRouter initialEntries={["/settings?tab=services"]}>
				<Settings />
			</MemoryRouter>,
		);

		await userEvent.click(screen.getByRole("button", { name: "Add service" }));
		expect(screen.getByText("New service")).toBeInTheDocument();
		await userEvent.keyboard("{Escape}");

		await userEvent.click(screen.getByRole("tab", { name: "Dashboards" }));
		expect(screen.getByRole("heading", { name: "Dashboards" })).toBeInTheDocument();
		expect(screen.getByRole("button", { name: "New dashboard" })).toBeInTheDocument();
	});
});
