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

const save = vi.fn(async () => undefined);
const remove = vi.fn(async () => undefined);
const rename = vi.fn(async () => undefined);
const setCrossDomain = vi.fn(async () => undefined);
const setNamedFilter = vi.fn();
const parseFilterString = vi.fn(async () => null);
let savedFilters: SavedFilter[] = [];

vi.mock("@olenbetong/appframe-react", async (importOriginal) => ({
	...(await importOriginal<Record<string, unknown>>()),
	setNamedFilter: (...args: unknown[]) => setNamedFilter(...args),
	useSavedFilters: () => ({
		filters: savedFilters,
		mine: savedFilters.filter((filter) => filter.createdByUser),
		shared: savedFilters.filter((filter) => !filter.createdByUser),
		loading: false,
		error: null,
		refresh: vi.fn(),
		save,
		rename,
		remove,
		setCrossDomain,
	}),
	useFilterConversion: () => ({
		filterString: "[PersonID] Like '%a%'",
		isConverting: false,
		error: null,
		parseFilterString,
	}),
}));

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

const FIELDS: FieldMetadata[] = [
	{ name: "PersonID", caption: "Person ID", type: "string", nullable: true },
	{ name: "Age", caption: "Age", type: "number" },
];

function savedFilter(overrides: Partial<SavedFilter> & Pick<SavedFilter, "filterId" | "name">): SavedFilter {
	return {
		primKey: `pk-${overrides.filterId}`,
		criteria: "[PersonID] Like '%a%'",
		dbObjectId: "aviw_Test_View",
		accessLevel: "Manager",
		createdByUser: true,
		createdBy: "bvh",
		domain: "OB",
		crossDomain: false,
		isPersonallyShared: false,
		hideCriteria: false,
		headerText: null,
		comment: null,
		parameters: null,
		reportTitle: null,
		created: null,
		updated: null,
		...overrides,
	};
}

function renderBuilder(props: Record<string, unknown> = {}) {
	return render(<FilterBuilder fields={FIELDS} viewName="aviw_Test_View" variant="inline" {...props} />);
}

describe("FilterBuilder", () => {
	beforeEach(() => {
		vi.clearAllMocks();
		savedFilters = [savedFilter({ filterId: 1, name: "Agder" })];
	});

	it("renders the saved filters beside the editor", () => {
		renderBuilder();

		expect(screen.getByRole("button", { name: "New Filter" })).toBeTruthy();
		expect(screen.getByRole("button", { name: "Agder" })).toBeTruthy();
		expect(screen.getByLabelText("Add condition")).toBeTruthy();
	});

	it("disables save and share until a filter is selected", () => {
		renderBuilder();

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

	it("disables save as while the filter is empty", () => {
		renderBuilder();

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

	it("parses the criteria of a selected filter back into the tree", async () => {
		renderBuilder();
		await userEvent.click(screen.getByRole("button", { name: "Agder" }));

		expect(parseFilterString).toHaveBeenCalledWith("[PersonID] Like '%a%'");
	});

	it("applies the pruned filter to the data object", async () => {
		let dataObject = {} as never;
		renderBuilder({
			dataObject,
			defaultValue: {
				type: "group",
				mode: "and",
				items: [{ type: "expression", column: "PersonID", operator: "like", value: "a", valueType: "string" }],
			},
		});

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

		expect(setNamedFilter).toHaveBeenCalledWith(
			dataObject,
			"filterBuilder",
			expect.objectContaining({ type: "group" }),
			{ type: "filterObject" },
		);
	});

	it("reports an empty filter as null", async () => {
		let onApply = vi.fn();
		renderBuilder({ onApply });

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

		expect(onApply).toHaveBeenCalledWith(null, "[PersonID] Like '%a%'");
	});

	it("saves under a new name and replaces a filter that already uses it", async () => {
		renderBuilder({
			defaultValue: {
				type: "group",
				mode: "and",
				items: [{ type: "expression", column: "PersonID", operator: "like", value: "a", valueType: "string" }],
			},
		});

		await userEvent.click(screen.getByRole("button", { name: "Save as" }));
		await userEvent.type(screen.getByRole("textbox", { name: "Filter name" }), "Agder");
		await userEvent.click(screen.getByRole("button", { name: "Replace" }));

		await waitFor(() =>
			expect(save).toHaveBeenCalledWith(
				expect.objectContaining({ filterId: 1, name: "Agder", criteria: "[PersonID] Like '%a%'" }),
			),
		);
	});
});
