import type { Report, ReportParameter, ReportParameterValues, SavedFilter } from "@olenbetong/appframe-core";
import { render, screen, waitFor, within } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { beforeEach, describe, expect, it, vi } from "vitest";

const download = vi.fn(async () => undefined);
const openRendered = vi.fn(async () => undefined);
const print = vi.fn(async () => undefined);
const save = vi.fn(async () => undefined);
const convertFilter = vi.fn(async () => ({ filterObject: null, filterString: "", whereClause: "" }));

let report: Report | null = null;
let parameters: ReportParameter[] = [];
let defaultValues: ReportParameterValues = {};
let savedFilters: SavedFilter[] = [];
let initialFilter: string | null = null;

vi.mock("@olenbetong/appframe-core", async (importOriginal) => ({
	...(await importOriginal<Record<string, unknown>>()),
	convertFilter: (...args: unknown[]) => convertFilter(...(args as [])),
}));

vi.mock("@olenbetong/appframe-react", async (importOriginal) => ({
	...(await importOriginal<Record<string, unknown>>()),
	setNamedFilter: vi.fn(),
	useFilterFields: () => ({ fields: [], loading: false, error: null, refresh: vi.fn() }),
	useFilterConversion: () => ({
		filterString: "[ProjectID] = 1",
		isConverting: false,
		error: null,
		parseFilterString: vi.fn(async () => null),
	}),
	useReport: () => ({
		report,
		parameters,
		initialFilter,
		defaultValues,
		loading: false,
		error: null,
		refresh: vi.fn(),
	}),
	useReportRender: () => ({
		open: openRendered,
		print,
		download,
		render: vi.fn(),
		cancel: vi.fn(),
		isRendering: false,
		progress: null,
		error: null,
	}),
	useSavedFilters: () => ({
		filters: savedFilters,
		mine: savedFilters,
		shared: [],
		loading: false,
		error: null,
		refresh: vi.fn(),
		save,
		rename: vi.fn(),
		remove: vi.fn(),
		setCrossDomain: vi.fn(),
	}),
}));

const { ReportLauncher } = await import("./ReportLauncher.js");

function makeReport(overrides: Partial<Report> = {}): Report {
	return {
		reportId: "arpt_Test_Report",
		shortId: 4711,
		title: "Test report",
		description: null,
		viewName: "aviw_Test_View",
		initialFilter: null,
		hasTitle: true,
		hasHeader: true,
		formats: ["PDF", "XLSX"],
		timeout: null,
		...overrides,
	};
}

function savedFilter(overrides: Partial<SavedFilter> = {}): SavedFilter {
	return {
		filterId: 1,
		primKey: "pk-1",
		name: "Mitt filter",
		criteria: "[ProjectID] = 1",
		dbObjectId: "aviw_Test_View",
		accessLevel: "Manager",
		createdByUser: true,
		createdBy: "bvh",
		domain: "OB",
		crossDomain: false,
		isPersonallyShared: false,
		hideCriteria: true,
		headerText: "Lagret overskrift",
		comment: null,
		parameters: '<Parameters Version="1"><Parameter Name="Period" Type="Integer">202601</Parameter></Parameters>',
		reportTitle: null,
		created: null,
		updated: null,
		...overrides,
	};
}

function numberParameter(overrides: Partial<ReportParameter> = {}): ReportParameter {
	return {
		name: "Period",
		label: "Periode",
		description: null,
		type: "Integer",
		editor: "number",
		defaultValue: null,
		items: [],
		recordSource: null,
		valueMember: null,
		displayMember: null,
		sortOrder: 0,
		lookupWidth: null,
		lookupHeight: null,
		...overrides,
	};
}

function renderLauncher(props: Record<string, unknown> = {}) {
	return render(<ReportLauncher reportId="arpt_Test_Report" variant="inline" {...props} />);
}

describe("ReportLauncher", () => {
	beforeEach(() => {
		vi.clearAllMocks();
		report = makeReport();
		parameters = [];
		defaultValues = {};
		savedFilters = [];
		initialFilter = null;
	});

	it("tells the user when the report is not available to them", () => {
		report = null;
		renderLauncher();

		expect(screen.getByRole("alert").textContent).toContain("do not have access");
	});

	it("shows the report option fields the report has bands for", () => {
		renderLauncher();

		expect(screen.getByLabelText("Report name")).toHaveValue("Test report");
		expect(screen.getByLabelText("Optional text (at the top of every page)")).toBeInTheDocument();
	});

	it("puts the parameters and the criteria in a tab each, with the options below both", async () => {
		parameters = [numberParameter()];
		defaultValues = { Period: null };
		renderLauncher();

		expect(screen.getByLabelText("Periode")).toBeInTheDocument();
		expect(screen.queryByLabelText("Filter string")).toBeNull();

		await userEvent.click(screen.getByText("Filter"));

		expect(screen.queryByLabelText("Periode")).toBeNull();
		expect(screen.getByLabelText("Filter string")).toBeInTheDocument();
		// The options apply to the report whichever tab is open.
		expect(screen.getByLabelText("Report name")).toBeInTheDocument();
	});

	it("can start on the filter tab", () => {
		parameters = [numberParameter()];
		renderLauncher({ defaultTab: "filter" });

		expect(screen.getByLabelText("Filter string")).toBeInTheDocument();
		expect(screen.queryByLabelText("Periode")).toBeNull();
	});

	it("names the report and its number in the dialog heading", () => {
		render(<ReportLauncher reportId="arpt_Test_Report" />);

		expect(screen.getByRole("heading").textContent).toBe("Report: Test report (ID: 4711)");
	});

	it("hides the title and header fields when the report has no such bands", () => {
		report = makeReport({ hasTitle: false, hasHeader: false });
		renderLauncher();

		expect(screen.queryByLabelText("Report name")).toBeNull();
		expect(screen.queryByLabelText("Optional text (at the top of every page)")).toBeNull();
	});

	it("offers the report's first format on the save button and the rest in a menu", async () => {
		renderLauncher();

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

		expect(download).toHaveBeenCalledWith(
			expect.objectContaining({ format: "PDF", fileName: "Test report", filter: "[ProjectID] = 1" }),
		);
		expect(screen.getByRole("button", { name: "More formats" })).toBeInTheDocument();
	});

	it("does not offer a format menu for a report with a single format", () => {
		report = makeReport({ formats: ["PDF"] });
		renderLauncher();

		expect(screen.queryByRole("button", { name: "More formats" })).toBeNull();
	});

	it("previews and prints as PDF", async () => {
		renderLauncher();

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

		expect(openRendered).toHaveBeenCalledWith(expect.objectContaining({ format: "PDF" }));
		expect(print).toHaveBeenCalledWith(expect.objectContaining({ format: "PDF" }));
	});

	it("cannot preview or print a report that has no PDF format", () => {
		report = makeReport({ formats: ["CSV"] });
		renderLauncher();

		expect(screen.getByRole("button", { name: "Preview" }).hasAttribute("disabled")).toBe(true);
		expect(screen.getByRole("button", { name: "Print" }).hasAttribute("disabled")).toBe(true);
	});

	it("sends the header text and the hide-criteria flag with the render", async () => {
		renderLauncher();

		await userEvent.type(screen.getByLabelText("Optional text (at the top of every page)"), "Hei");
		await userEvent.click(screen.getByLabelText("Do not show the filter criteria at the top of the pages"));
		await userEvent.click(screen.getByRole("button", { name: "Save as PDF" }));

		expect(download).toHaveBeenCalledWith(expect.objectContaining({ headerText: "Hei", hideFilterString: true }));
	});

	it("restores the report options stored with a saved filter", async () => {
		parameters = [numberParameter()];
		defaultValues = { Period: null };
		savedFilters = [savedFilter()];
		renderLauncher();

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

		await waitFor(() => expect(screen.getByLabelText("Periode")).toHaveValue(202601));
		expect(screen.getByLabelText("Optional text (at the top of every page)")).toHaveValue("Lagret overskrift");
		expect(
			(screen.getByLabelText("Do not show the filter criteria at the top of the pages") as HTMLInputElement).checked,
		).toBe(true);
	});

	it("stores the report options on the filter when it is saved", async () => {
		parameters = [];
		// "Save as" is only offered for a filter with conditions, so the launcher
		// starts from the report's own filter.
		convertFilter.mockResolvedValue({
			filterObject: {
				type: "group",
				mode: "and",
				items: [{ type: "expression", column: "ProjectID", operator: "equals", value: 1, valueType: "number" }],
			},
			filterString: "[ProjectID] = 1",
			whereClause: "",
		} as never);
		initialFilter = "[ProjectID] = 1";
		renderLauncher();

		await userEvent.type(screen.getByLabelText("Optional text (at the top of every page)"), "Hei");
		await userEvent.click(screen.getByRole("button", { name: "Save as" }));

		let dialog = screen.getByRole("dialog");
		await userEvent.type(within(dialog).getByRole("textbox", { name: "Filter name" }), "Nytt filter");
		await userEvent.click(within(dialog).getByRole("button", { name: "Save" }));

		await waitFor(() =>
			expect(save).toHaveBeenCalledWith(expect.objectContaining({ headerText: "Hei", hideCriteria: false })),
		);
	});
});
