import sensible from "@fastify/sensible";
import Fastify, { type FastifyInstance } from "fastify";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { documentationSchemaAjv } from "../utils/documentation-schema-keywords.js";

const { fetchMock, requireAuthMock } = vi.hoisted(() => ({
	fetchMock: vi.fn(),
	requireAuthMock: vi.fn(async () => {}),
}));

vi.mock("../plugins/auth.js", () => ({
	requireAuth: requireAuthMock,
}));

function jsonResponse(body: unknown, status = 200): Response {
	return {
		ok: status >= 200 && status < 300,
		status,
		json: async () => body,
	} as Response;
}

function createEmaRow(overrides: Partial<Record<string, unknown>> = {}): Record<string, unknown> {
	return {
		category: "Human",
		medicine_status: "Authorised",
		name_of_medicine: "Aspirin 500 mg tablets",
		international_non_proprietary_name_common_name: "Acetylsalicylic acid",
		active_substance: "Acetylsalicylic acid",
		marketing_authorisation_developer_applicant_holder: "Bayer",
		therapeutic_area_mesh: "Pain",
		therapeutic_indication: "Pain relief",
		atc_code_human: "N02BA01",
		generic_or_hybrid: "No",
		biosimilar: "No",
		marketing_authorisation_date: "01/02/2024",
		ema_product_number: "EMA-ASPIRIN",
		...overrides,
	};
}

async function buildApp(): Promise<FastifyInstance> {
	const { medicationEnrichmentRoutes } = await import("../routes/medication-enrichment.js");
	const app = Fastify({ logger: false, ajv: documentationSchemaAjv });
	await app.register(sensible);
	await app.register(medicationEnrichmentRoutes);
	await app.ready();
	return app;
}

describe("medication enrichment", () => {
	beforeEach(() => {
		vi.resetModules();
		vi.clearAllMocks();
		fetchMock.mockReset();
		requireAuthMock.mockReset();
		requireAuthMock.mockImplementation(async () => {});
		vi.stubGlobal("fetch", fetchMock);
	});

	it("normalizes German ingredient queries for EMA-backed search results", async () => {
		const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");

		fetchMock.mockImplementation((url: string) => {
			if (url.includes("medicines-output-medicines_json-report_en.json")) {
				return Promise.resolve(
					jsonResponse([
						createEmaRow({
							name_of_medicine: "Tylenol 500 mg tablets",
							international_non_proprietary_name_common_name: "Acetaminophen",
							active_substance: "Acetaminophen",
							ema_product_number: "EMA-TYLENOL",
						}),
						createEmaRow({
							name_of_medicine: "Ibuprofen 400 mg tablets",
							international_non_proprietary_name_common_name: "Ibuprofen",
							active_substance: "Ibuprofen",
							ema_product_number: "EMA-IBUPROFEN",
						}),
					])
				);
			}
			if (url.includes("/drugs.json?name=")) {
				return Promise.resolve(jsonResponse({ drugGroup: { conceptGroup: [] } }));
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(jsonResponse({ results: [] }));
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await searchMedicationEnrichment("Paracetamol 500 mg", 5);

		expect(response.normalizedQuery).toBe("paracetamol 500 mg");
		expect(response.results).toHaveLength(1);
		expect(response.results[0]).toMatchObject({
			code: "EMA-TYLENOL",
			name: "Tylenol 500 mg tablets",
			matchType: "ingredient",
			source: "ema",
		});
	});

	it("requires auth and returns EMA search results from the route", async () => {
		const app = await buildApp();
		fetchMock.mockImplementation((url: string) => {
			if (url.includes("/drugs.json?name=")) {
				return Promise.resolve(jsonResponse({ drugGroup: { conceptGroup: [] } }));
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(jsonResponse({ results: [] }));
			}
			if (url.includes("medicines-output-medicines_json-report_en.json")) {
				return Promise.resolve(jsonResponse([createEmaRow()]));
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await app.inject({
			method: "GET",
			url: "/medication-enrichment/search?q=aspirin&limit=1",
		});

		expect(response.statusCode).toBe(200);
		expect(requireAuthMock).toHaveBeenCalledTimes(1);
		expect(response.json()).toMatchObject({
			query: "aspirin",
			normalizedQuery: "aspirin",
			hasMore: false,
			results: [
				{
					code: "EMA-ASPIRIN",
					name: "Aspirin 500 mg tablets",
					source: "ema",
				},
			],
		});

		await app.close();
	});

	it("falls back from EMA to RxNorm and openFDA search results when EMA has no match", async () => {
		const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");

		fetchMock.mockImplementation((url: string) => {
			if (url.includes("medicines-output-medicines_json-report_en.json")) {
				return Promise.resolve(jsonResponse([createEmaRow()]));
			}
			if (url.includes("/drugs.json?name=semaglutide")) {
				return Promise.resolve(
					jsonResponse({
						drugGroup: {
							conceptGroup: [
								{
									tty: "SBD",
									conceptProperties: [
										{
											rxcui: "12345",
											name: "Semaglutide 0.25 MG Oral Tablet [Wegovy]",
											synonym: "Wegovy 0.25 mg oral tablet",
										},
									],
								},
							],
						},
					})
				);
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(
					jsonResponse({
						results: [
							{
								product_ndc: "00011-1111",
								brand_name: "Ozempic",
								generic_name: "Semaglutide",
								dosage_form: "Tablet",
								marketing_start_date: "20240101",
								packaging: [{ description: "2 blisters in 1 carton / 10 tablets in 1 blister" }],
							},
						],
					})
				);
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await searchMedicationEnrichment("Semaglutide", 3);

		expect(response.hasMore).toBe(false);
		expect(response.results).toEqual(
			expect.arrayContaining([
				expect.objectContaining({
					code: "12345",
					name: "Wegovy",
					genericName: "Semaglutide",
					source: "rxnorm",
				}),
				expect.objectContaining({
					code: "00011-1111",
					name: "Ozempic",
					genericName: "Semaglutide",
					source: "openfda",
				}),
			])
		);
		expect(response.results.find((result) => result.code === "00011-1111")?.packageOptions).toEqual([
			{
				label: "2 blisters in 1 carton / 10 tablets in 1 blister",
				description: "2 blisters in 1 carton / 10 tablets in 1 blister",
				packageType: "blister",
				packCount: 1,
				blistersPerPack: 2,
				pillsPerBlister: 10,
				totalPills: 20,
				looseTablets: 0,
				packageAmountValue: null,
				packageAmountUnit: null,
			},
		]);
	});

	it("prioritizes results with package sizes before source-only matches", async () => {
		const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");

		fetchMock.mockImplementation((url: string) => {
			if (url.includes("medicines-output-medicines_json-report_en.json")) {
				return Promise.resolve(jsonResponse([createEmaRow()]));
			}
			if (url.includes("/drugs.json?name=")) {
				return Promise.resolve(
					jsonResponse({
						drugGroup: {
							conceptGroup: [
								{
									tty: "SBD",
									conceptProperties: [
										{
											rxcui: "1191",
											name: "Aspirin 500 MG Oral Tablet [Aspirin]",
											synonym: "Aspirin 500 mg oral tablet",
										},
									],
								},
							],
						},
					})
				);
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(
					jsonResponse({
						results: [
							{
								product_ndc: "00011-1111",
								brand_name: "Bayer Aspirin",
								generic_name: "Acetylsalicylic acid",
								dosage_form: "Tablet",
								marketing_start_date: "20240101",
								packaging: [{ description: "2 blisters in 1 carton / 10 tablets in 1 blister" }],
							},
						],
					})
				);
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await searchMedicationEnrichment("Aspirin", 3);

		expect(response.hasMore).toBe(false);
		expect(response.results).toHaveLength(3);
		expect(response.results[0]).toMatchObject({
			code: "00011-1111",
			source: "openfda",
		});
		expect(response.results[1]).toMatchObject({
			code: "1191",
			source: "rxnorm",
		});
		expect(response.results[2]).toMatchObject({
			code: "EMA-ASPIRIN",
			source: "ema",
		});
	});

	it("sorts richer package hits ahead of package-bearing results with fewer options", async () => {
		const { searchMedicationEnrichment } = await import("../services/medication-enrichment.js");

		fetchMock.mockImplementation((url: string) => {
			if (url.includes("medicines-output-medicines_json-report_en.json")) {
				return Promise.resolve(jsonResponse([createEmaRow()]));
			}
			if (url.includes("/drugs.json?name=")) {
				return Promise.resolve(jsonResponse({ drugGroup: { conceptGroup: [] } }));
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(
					jsonResponse({
						results: [
							{
								product_ndc: "00011-1111",
								brand_name: "Ibuprofen Max",
								generic_name: "Ibuprofen",
								dosage_form: "Tablet",
								marketing_start_date: "20240101",
								packaging: [{ description: "60 tablets in 1 bottle" }, { description: "120 tablets in 1 bottle" }],
							},
							{
								product_ndc: "00022-2222",
								brand_name: "Ibuprofen Compact",
								generic_name: "Ibuprofen",
								dosage_form: "Tablet",
								marketing_start_date: "20240101",
								packaging: [{ description: "20 tablets in 1 blister" }],
							},
						],
					})
				);
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await searchMedicationEnrichment("Ibuprofen", 3);

		expect(response.results.slice(0, 2)).toMatchObject([
			{
				code: "00011-1111",
				source: "openfda",
			},
			{
				code: "00022-2222",
				source: "openfda",
			},
		]);
		expect(response.results[0].packageOptions).toHaveLength(2);
		expect(response.results[1].packageOptions).toHaveLength(1);
	});

	it("validates malformed search requests", async () => {
		const app = await buildApp();

		const response = await app.inject({
			method: "GET",
			url: "/medication-enrichment/search?q=",
		});

		expect(response.statusCode).toBe(400);
		expect(fetchMock).not.toHaveBeenCalled();

		await app.close();
	});

	it("returns enrichment suggestions with optional RxNorm strength data", async () => {
		const app = await buildApp();
		fetchMock
			.mockResolvedValueOnce(
				jsonResponse([
					createEmaRow({
						name_of_medicine: "Tylenol 500 mg tablets",
						international_non_proprietary_name_common_name: "Acetaminophen",
						active_substance: "Acetaminophen",
						ema_product_number: "EMA-TYLENOL",
					}),
				])
			)
			.mockResolvedValueOnce(jsonResponse({ idGroup: { rxnormId: ["161"] } }))
			.mockResolvedValueOnce(
				jsonResponse({
					relatedGroup: {
						conceptGroup: [
							{
								conceptProperties: [
									{ name: "Acetaminophen 500 MG Oral Tablet" },
									{ name: "Acetaminophen 650 MG Oral Tablet" },
								],
							},
						],
					},
				})
			);

		const response = await app.inject({
			method: "POST",
			url: "/medication-enrichment/enrich",
			payload: {
				query: "Paracetamol",
				name: "Tylenol 500 mg tablets",
				genericName: "Acetaminophen",
			},
		});

		expect(response.statusCode).toBe(200);
		expect(response.json()).toMatchObject({
			selection: {
				name: "Tylenol 500 mg tablets",
				genericName: "Acetaminophen",
				source: "ema+rxnorm",
			},
			suggestions: {
				medicationForm: "tablet",
				strengthOptions: [
					{ label: "500 mg", pillWeightMg: 500, doseUnit: "mg" },
					{ label: "650 mg", pillWeightMg: 650, doseUnit: "mg" },
				],
			},
			meta: {
				rxNormMatched: true,
				openFdaMatched: false,
				partial: false,
				note: null,
			},
		});

		await app.close();
	});

	it("includes package suggestions from openFDA fallback in route responses", async () => {
		const app = await buildApp();
		fetchMock.mockImplementation((url: string) => {
			if (url.includes("medicines-output-medicines_json-report_en.json")) {
				return Promise.resolve(
					jsonResponse([
						createEmaRow({
							name_of_medicine: "Tylenol 500 mg tablets",
							international_non_proprietary_name_common_name: "Acetaminophen",
							active_substance: "Acetaminophen",
							ema_product_number: "EMA-TYLENOL",
						}),
					])
				);
			}
			if (url.includes("/rxcui.json?name=acetaminophen&search=2")) {
				return Promise.resolve(jsonResponse({ idGroup: {} }));
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(
					jsonResponse({
						results: [
							{
								product_ndc: "00011-1111",
								brand_name: "Tylenol",
								generic_name: "Acetaminophen",
								dosage_form: "Tablet",
								active_ingredients: [{ name: "Acetaminophen", strength: "500 mg" }],
								packaging: [{ description: "30 tablets in 1 bottle" }],
							},
						],
					})
				);
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await app.inject({
			method: "POST",
			url: "/medication-enrichment/enrich",
			payload: {
				query: "Paracetamol",
				name: "Tylenol 500 mg tablets",
				genericName: "Acetaminophen",
			},
		});

		expect(response.statusCode).toBe(200);
		expect(response.json()).toMatchObject({
			selection: {
				name: "Tylenol 500 mg tablets",
				genericName: "Acetaminophen",
				source: "ema+openfda",
			},
			suggestions: {
				medicationForm: "tablet",
				strengthOptions: [{ label: "500 mg", pillWeightMg: 500, doseUnit: "mg" }],
				packageOptions: [
					{
						label: "30 tablets in 1 bottle",
						description: "30 tablets in 1 bottle",
						packageType: "bottle",
						packCount: 1,
						blistersPerPack: null,
						pillsPerBlister: null,
						totalPills: 30,
						looseTablets: 30,
						packageAmountValue: null,
						packageAmountUnit: null,
					},
				],
			},
			meta: {
				rxNormMatched: false,
				openFdaMatched: true,
				partial: false,
				note: null,
			},
		});

		await app.close();
	});

	it("keeps incomplete-coverage messaging honest when RxNorm enrichment fails", async () => {
		const { enrichMedicationSelection } = await import("../services/medication-enrichment.js");

		fetchMock.mockImplementation((url: string) => {
			if (url.includes("medicines-output-medicines_json-report_en.json")) {
				return Promise.resolve(
					jsonResponse([
						createEmaRow({
							name_of_medicine: "Tylenol 500 mg tablets",
							international_non_proprietary_name_common_name: "Acetaminophen",
							active_substance: "Acetaminophen",
							ema_product_number: "EMA-TYLENOL",
						}),
					])
				);
			}
			if (url.includes("/rxcui.json?name=acetaminophen&search=2")) {
				return Promise.reject(new Error("rxnorm timeout"));
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(jsonResponse({ results: [] }));
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await enrichMedicationSelection({
			query: "Paracetamol",
			name: "Tylenol 500 mg tablets",
			genericName: "Acetaminophen",
		});

		expect(response.selection.source).toBe("ema");
		expect(response.suggestions.strengthOptions).toEqual([]);
		expect(response.meta).toEqual({
			rxNormMatched: false,
			openFdaMatched: false,
			partial: true,
			note: "Returned EMA enrichment without RxNorm suggestions.",
		});
	});

	it("enriches RxNorm selections by code and falls back to openFDA without best-match guessing", async () => {
		const { enrichMedicationSelection } = await import("../services/medication-enrichment.js");

		fetchMock.mockImplementation((url: string) => {
			if (url.includes("/rxcui/12345/related.json")) {
				return Promise.resolve(
					jsonResponse({
						relatedGroup: {
							conceptGroup: [],
						},
					})
				);
			}
			if (url.includes("api.fda.gov/drug/ndc.json")) {
				return Promise.resolve(
					jsonResponse({
						results: [
							{
								product_ndc: "00011-1111",
								brand_name: "Ozempic",
								generic_name: "Semaglutide",
								dosage_form: "Tablet",
								active_ingredients: [{ name: "Semaglutide", strength: "2 mg" }],
							},
						],
					})
				);
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await enrichMedicationSelection({
			query: "Ozempic",
			name: "Ozempic",
			genericName: "Semaglutide",
			code: "12345",
			source: "rxnorm",
		});

		expect(response).toMatchObject({
			selection: {
				name: "Ozempic",
				genericName: "Semaglutide",
				source: "rxnorm+openfda",
			},
			suggestions: {
				medicationForm: "tablet",
				strengthOptions: [{ label: "2 mg", pillWeightMg: 2, doseUnit: "mg" }],
			},
			meta: {
				rxNormMatched: false,
				openFdaMatched: true,
				partial: false,
				note: null,
			},
		});
	});

	it("enriches openFDA selections by code and augments them with RxNorm strength data", async () => {
		const { enrichMedicationSelection } = await import("../services/medication-enrichment.js");

		fetchMock.mockImplementation((url: string) => {
			if (url.includes("search=product_ndc%3A%2200011-1111%22")) {
				return Promise.resolve(
					jsonResponse({
						results: [
							{
								product_ndc: "00011-1111",
								brand_name: "US Ibuprofen",
								generic_name: "Ibuprofen",
								dosage_form: "Tablet",
								active_ingredients: [{ name: "Ibuprofen", strength: "200 mg" }],
								packaging: [{ description: "100 mL in 1 bottle" }],
							},
						],
					})
				);
			}
			if (url.includes("/rxcui.json?name=ibuprofen&search=2")) {
				return Promise.resolve(jsonResponse({ idGroup: { rxnormId: ["161"] } }));
			}
			if (url.includes("/rxcui/161/related.json")) {
				return Promise.resolve(
					jsonResponse({
						relatedGroup: {
							conceptGroup: [
								{
									conceptProperties: [
										{ name: "Ibuprofen 200 MG Oral Tablet" },
										{ name: "Ibuprofen 400 MG Oral Tablet" },
									],
								},
							],
						},
					})
				);
			}
			return Promise.reject(new Error(`Unexpected URL: ${url}`));
		});

		const response = await enrichMedicationSelection({
			query: "US Ibuprofen",
			name: "US Ibuprofen",
			genericName: "Ibuprofen",
			code: "00011-1111",
			source: "openfda",
		});

		expect(response).toMatchObject({
			selection: {
				name: "US Ibuprofen",
				genericName: "Ibuprofen",
				source: "rxnorm+openfda",
			},
			suggestions: {
				medicationForm: "tablet",
				strengthOptions: [
					{ label: "200 mg", pillWeightMg: 200, doseUnit: "mg" },
					{ label: "400 mg", pillWeightMg: 400, doseUnit: "mg" },
				],
				packageOptions: [
					{
						label: "100 mL in 1 bottle",
						description: "100 mL in 1 bottle",
						packageType: "liquid_container",
						packCount: 1,
						blistersPerPack: null,
						pillsPerBlister: null,
						totalPills: 100,
						looseTablets: 100,
						packageAmountValue: 100,
						packageAmountUnit: "ml",
					},
				],
			},
			meta: {
				rxNormMatched: true,
				openFdaMatched: true,
				partial: false,
				note: null,
			},
		});
	});

	it("returns not found when an explicit selection cannot be resolved", async () => {
		const app = await buildApp();
		fetchMock.mockResolvedValueOnce(jsonResponse([createEmaRow()]));

		const response = await app.inject({
			method: "POST",
			url: "/medication-enrichment/enrich",
			payload: {
				query: "Unknown",
				name: "Completely Different Medication",
				genericName: "No match",
			},
		});

		expect(response.statusCode).toBe(404);
		expect(response.json()).toMatchObject({
			code: "MEDICATION_ENRICHMENT_NOT_FOUND",
			error: "Selected medication could not be resolved.",
		});

		await app.close();
	});

	it("keeps split module exports aligned with the canonical enrichment service", async () => {
		const indexExports = await import("../services/medication-enrichment/index.js");
		const searchExports = await import("../services/medication-enrichment/search.js");
		const adapterExports = await import("../services/medication-enrichment/adapters.js");
		const canonical = await import("../services/medication-enrichment.js");

		expect(indexExports.searchMedicationEnrichment).toBe(canonical.searchMedicationEnrichment);
		expect(indexExports.enrichMedicationSelection).toBe(canonical.enrichMedicationSelection);
		expect(searchExports.searchMedicationEnrichment).toBe(canonical.searchMedicationEnrichment);
		expect(adapterExports.MEDICATION_ENRICHMENT_SEARCH_DEFAULT_LIMIT).toBe(
			canonical.MEDICATION_ENRICHMENT_SEARCH_DEFAULT_LIMIT
		);
		expect(adapterExports.MEDICATION_ENRICHMENT_SEARCH_MAX_LIMIT).toBe(
			canonical.MEDICATION_ENRICHMENT_SEARCH_MAX_LIMIT
		);
	});

	it("returns transport-safe 503 payload when search lookup fails unexpectedly", async () => {
		const app = await buildApp();
		fetchMock.mockRejectedValue(new Error("network unavailable"));

		const response = await app.inject({
			method: "GET",
			url: "/medication-enrichment/search?q=aspirin&limit=1",
		});

		expect(response.statusCode).toBe(503);
		expect(response.json()).toEqual({
			error: "Medication enrichment is temporarily unavailable.",
			code: "MEDICATION_ENRICHMENT_UNAVAILABLE",
		});

		await app.close();
	});
});
