// @vitest-environment jsdom
import "@testing-library/jest-dom/vitest";

import { cleanup, render, screen, waitFor } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";
import { createTestAdapters } from "../../__tests__/adapters/in-memory-adapters.js";
import { AdapterProvider } from "../../contexts/adapter-context.js";
import { RulesEditionProvider } from "../../contexts/rules-edition-context.js";
import { SourceFetchPrompt } from "../source-fetch-prompt.js";

const MONSTER_MANUAL_REGEX = /Monster Manual/;

afterEach(cleanup);

const mockFetchAndCacheSource = vi.fn();
const mockUploadAndCacheSource = vi.fn();

// Uses context mock because fetchAndCacheSource/uploadAndCacheSource involve
// real fetch() calls. The test controls success/failure to verify the
// component's loading and error UI, not the fetching logic itself.
vi.mock("../../contexts/bestiary-context.js", () => ({
	useBestiaryContext: () => ({
		fetchAndCacheSource: mockFetchAndCacheSource,
		uploadAndCacheSource: mockUploadAndCacheSource,
	}),
}));

function renderPrompt(sourceCode = "MM") {
	const onSourceLoaded = vi.fn();
	const adapters = createTestAdapters();
	adapters.bestiaryIndex = {
		...adapters.bestiaryIndex,
		getDefaultFetchUrl: (code: string) =>
			`https://example.com/bestiary/${code}.json`,
		getSourceDisplayName: (code: string) =>
			code === "MM" ? "Monster Manual" : code,
	};
	const result = render(
		<RulesEditionProvider>
			<AdapterProvider adapters={adapters}>
				<SourceFetchPrompt
					sourceCode={sourceCode}
					onSourceLoaded={onSourceLoaded}
				/>
			</AdapterProvider>
		</RulesEditionProvider>,
	);
	return { ...result, onSourceLoaded };
}

describe("SourceFetchPrompt", () => {
	afterEach(() => {
		vi.clearAllMocks();
	});

	it("renders source name, URL input, Load and Upload buttons", () => {
		renderPrompt();
		expect(screen.getByText(MONSTER_MANUAL_REGEX)).toBeInTheDocument();
		expect(
			screen.getByDisplayValue("https://example.com/bestiary/MM.json"),
		).toBeInTheDocument();
		expect(screen.getByText("Load")).toBeInTheDocument();
		expect(screen.getByText("Upload file")).toBeInTheDocument();
	});

	it("Load calls fetchAndCacheSource and onSourceLoaded on success", async () => {
		mockFetchAndCacheSource.mockResolvedValueOnce({ skippedNames: [] });
		const user = userEvent.setup();
		const { onSourceLoaded } = renderPrompt();

		await user.click(screen.getByText("Load"));

		await waitFor(() => {
			expect(mockFetchAndCacheSource).toHaveBeenCalledWith(
				"MM",
				"https://example.com/bestiary/MM.json",
			);
			expect(onSourceLoaded).toHaveBeenCalled();
		});
	});

	it("fetch error shows error message", async () => {
		mockFetchAndCacheSource.mockRejectedValueOnce(new Error("Network error"));
		const user = userEvent.setup();
		renderPrompt();

		await user.click(screen.getByText("Load"));

		await waitFor(() => {
			expect(screen.getByText("Network error")).toBeInTheDocument();
		});
	});

	it("upload file calls uploadAndCacheSource and onSourceLoaded", async () => {
		mockUploadAndCacheSource.mockResolvedValueOnce(undefined);
		const user = userEvent.setup();
		const { onSourceLoaded } = renderPrompt();

		const file = new File(['{"monster":[]}'], "bestiary-mm.json", {
			type: "application/json",
		});
		const fileInput = document.querySelector(
			'input[type="file"]',
		) as HTMLInputElement;
		await user.upload(fileInput, file);

		await waitFor(() => {
			expect(mockUploadAndCacheSource).toHaveBeenCalledWith("MM", {
				monster: [],
			});
			expect(onSourceLoaded).toHaveBeenCalled();
		});
	});

	it("upload error shows error message", async () => {
		mockUploadAndCacheSource.mockRejectedValueOnce(new Error("Invalid format"));
		const user = userEvent.setup();
		renderPrompt();

		const file = new File(['{"bad": true}'], "bad.json", {
			type: "application/json",
		});
		const fileInput = document.querySelector(
			'input[type="file"]',
		) as HTMLInputElement;
		await user.upload(fileInput, file);

		await waitFor(() => {
			expect(screen.getByText("Invalid format")).toBeInTheDocument();
		});
	});
});
