import {
	cleanup,
	fireEvent,
	render,
	screen,
	waitFor,
} from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";

import { RepositoriesSettingsTab } from "./repositories-settings-tab";
import * as gitRepositoriesApi from "../../../api/git-repositories";
import * as sshKeysApi from "../../../api/ssh-keys";

const mockRepositories = [
	{
		id: "repo-1",
		name: "Main Repo",
		path: "/repos/main",
		project_id: "proj-1",
		owner_id: "user-1",
		is_mirror: false,
		remote_url: null,
		ssh_key_id: null,
		last_push: null,
		created_at: null,
	},
];

vi.mock("react-router-dom", async () => {
	const actual =
		await vi.importActual<typeof import("react-router-dom")>(
			"react-router-dom",
		);
	return {
		...actual,
		useParams: () => ({ projectId: "proj-1" }),
	};
});

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

describe("RepositoriesSettingsTab", () => {
	it("opens create dialog and clones using full URL by default", async () => {
		const listMock = vi
			.spyOn(gitRepositoriesApi, "listRepositories")
			.mockResolvedValue(mockRepositories);
		const createMock = vi
			.spyOn(gitRepositoriesApi, "createRepository")
			.mockResolvedValue(mockRepositories[0]);
		const parseMock = vi
			.spyOn(gitRepositoriesApi, "parseGitUrl")
			.mockResolvedValue({
				original_url: "https://github.com/user/repo.git",
				base_url: "https://github.com/user/repo.git",
				is_valid_clone_url: true,
				needs_parsing: false,
				host: "github.com",
				message: "Valid git repository URL",
				error_code: null,
			});
		vi.spyOn(sshKeysApi, "listSSHKeys").mockResolvedValue([]);

		render(<RepositoriesSettingsTab />);

		await waitFor(() => {
			expect(screen.getByText("Main Repo")).toBeInTheDocument();
		});

		fireEvent.click(screen.getByRole("button", { name: /add repository/i }));
		fireEvent.change(screen.getByPlaceholderText(/repository-name/i), {
			target: { value: "New Repo" },
		});
		fireEvent.change(
			screen.getByPlaceholderText(/https:\/\/github.com\/user\/repo.git/i),
			{
				target: { value: "https://github.com/user/repo.git" },
			},
		);

		await waitFor(() => {
			expect(parseMock).toHaveBeenCalledWith("https://github.com/user/repo.git");
		});

		fireEvent.click(screen.getByRole("button", { name: /clone repository/i }));

		await waitFor(() => {
			expect(createMock).toHaveBeenCalledWith("proj-1", {
				name: "New Repo",
				remote_url: "https://github.com/user/repo.git",
			});
		});
		expect(listMock).toHaveBeenCalledTimes(2);
	});

	it("switches to owner/repo mode and requires SSH key for generated SSH URL", async () => {
		const listMock = vi
			.spyOn(gitRepositoriesApi, "listRepositories")
			.mockResolvedValue(mockRepositories);
		const createMock = vi
			.spyOn(gitRepositoriesApi, "createRepository")
			.mockResolvedValue(mockRepositories[0]);
		vi.spyOn(sshKeysApi, "listSSHKeys").mockResolvedValue([
			{
				id: "key-1",
				name: "My Key",
				public_key: "ssh-ed25519 AAA...",
				created_at: "2024-01-01",
			},
		]);

		render(<RepositoriesSettingsTab />);

		await waitFor(() => {
			expect(screen.getByText("Main Repo")).toBeInTheDocument();
		});

		fireEvent.click(screen.getByRole("button", { name: /add repository/i }));
		fireEvent.change(screen.getByPlaceholderText(/repository-name/i), {
			target: { value: "New Repo" },
		});
		fireEvent.click(
			screen.getByRole("button", { name: /use owner\/repo instead/i }),
		);
		fireEvent.change(screen.getByPlaceholderText(/owner/i), {
			target: { value: "alice" },
		});
		fireEvent.change(screen.getByPlaceholderText(/repo-name/i), {
			target: { value: "demo" },
		});
		const sshSelect = screen.getByRole("combobox", { name: /ssh key/i });
		await waitFor(() =>
			expect(sshSelect.querySelector('option[value="key-1"]')).toBeTruthy(),
		);
		(sshSelect as HTMLSelectElement).value = "key-1";
		fireEvent.change(sshSelect);
		fireEvent.click(screen.getByRole("button", { name: /clone repository/i }));

		await waitFor(() => {
			expect(createMock).toHaveBeenCalledWith("proj-1", {
				name: "New Repo",
				remote_url: "git@git.commumedia.org:alice/demo.git",
				ssh_key_id: "key-1",
			});
		});
		expect(listMock).toHaveBeenCalledTimes(2);
	});

	it("accepts SSH URL in full URL mode when SSH key is selected", async () => {
		const listMock = vi
			.spyOn(gitRepositoriesApi, "listRepositories")
			.mockResolvedValue(mockRepositories);
		const createMock = vi
			.spyOn(gitRepositoriesApi, "createRepository")
			.mockResolvedValue(mockRepositories[0]);
		// SSH URLs are short-circuited client-side; parseGitUrl should NOT be called
		const parseMock = vi
			.spyOn(gitRepositoriesApi, "parseGitUrl")
			.mockResolvedValue({
				original_url: "",
				base_url: null,
				is_valid_clone_url: false,
				needs_parsing: false,
				host: null,
				message: "",
				error_code: null,
			});
		vi.spyOn(sshKeysApi, "listSSHKeys").mockResolvedValue([
			{
				id: "key-1",
				name: "My Key",
				public_key: "ssh-ed25519 AAA...",
				created_at: "2024-01-01",
			},
		]);

		render(<RepositoriesSettingsTab />);

		await waitFor(() => {
			expect(screen.getByText("Main Repo")).toBeInTheDocument();
		});

		fireEvent.click(screen.getByRole("button", { name: /add repository/i }));
		fireEvent.change(screen.getByPlaceholderText(/repository-name/i), {
			target: { value: "New Repo" },
		});
		fireEvent.change(
			screen.getByPlaceholderText(/https:\/\/github.com\/user\/repo.git/i),
			{
				target: { value: "git@github.com:user/repo.git" },
			},
		);

		// Client-side short-circuit shows valid without calling backend
		await waitFor(() => {
			expect(screen.getByText(/valid git url/i)).toBeInTheDocument();
		});
		expect(parseMock).not.toHaveBeenCalled();

		const sshSelect = screen.getByRole("combobox", { name: /ssh key/i });
		await waitFor(() =>
			expect(sshSelect.querySelector('option[value="key-1"]')).toBeTruthy(),
		);
		(sshSelect as HTMLSelectElement).value = "key-1";
		fireEvent.change(sshSelect);

		fireEvent.click(screen.getByRole("button", { name: /clone repository/i }));

		await waitFor(() => {
			expect(createMock).toHaveBeenCalledWith("proj-1", {
				name: "New Repo",
				remote_url: "git@github.com:user/repo.git",
				ssh_key_id: "key-1",
			});
		});
		expect(listMock).toHaveBeenCalledTimes(2);
	});

	it("rejects SSH URL in full URL mode without an SSH key", async () => {
		vi.spyOn(gitRepositoriesApi, "listRepositories").mockResolvedValue(
			mockRepositories,
		);
		const createMock = vi
			.spyOn(gitRepositoriesApi, "createRepository")
			.mockResolvedValue(mockRepositories[0]);
		vi.spyOn(sshKeysApi, "listSSHKeys").mockResolvedValue([
			{
				id: "key-1",
				name: "My Key",
				public_key: "ssh-ed25519 AAA...",
				created_at: "2024-01-01",
			},
		]);

		render(<RepositoriesSettingsTab />);

		await waitFor(() => {
			expect(screen.getByText("Main Repo")).toBeInTheDocument();
		});

		fireEvent.click(screen.getByRole("button", { name: /add repository/i }));
		fireEvent.change(screen.getByPlaceholderText(/repository-name/i), {
			target: { value: "New Repo" },
		});
		fireEvent.change(
			screen.getByPlaceholderText(/https:\/\/github.com\/user\/repo.git/i),
			{
				target: { value: "git@github.com:user/repo.git" },
			},
		);

		// Do NOT select an SSH key
		fireEvent.click(screen.getByRole("button", { name: /clone repository/i }));

		expect(
			screen.getByText(/an ssh key is required for ssh urls/i),
		).toBeInTheDocument();
		expect(createMock).not.toHaveBeenCalled();
	});

	it("shows validation when cloning without a remote url", async () => {
		vi.spyOn(gitRepositoriesApi, "listRepositories").mockResolvedValue(
			mockRepositories,
		);
		render(<RepositoriesSettingsTab />);

		await waitFor(() => {
			expect(screen.getByText("Main Repo")).toBeInTheDocument();
		});

		fireEvent.click(screen.getByRole("button", { name: /add repository/i }));
		fireEvent.change(screen.getByPlaceholderText(/repository-name/i), {
			target: { value: "New Repo" },
		});
		fireEvent.change(
			screen.getByPlaceholderText(/https:\/\/github.com\/user\/repo.git/i),
			{
				target: { value: "" },
			},
		);
		fireEvent.click(screen.getByRole("button", { name: /clone repository/i }));

		expect(
			screen.getByText(/remote url is required for advanced cloning/i),
		).toBeInTheDocument();
	});
});
