import { randomUUID } from "node:crypto";
import { sendPaymentConfirmationEmail } from "@kk/api/email";
import type { EmailMessage } from "@kk/api/email-queue";
import { creditRegistrationToAccount } from "@kk/api/routers/index";
import { db } from "@kk/db";
import { drinkkaart, drinkkaartTopup, registration } from "@kk/db/schema";
import { user } from "@kk/db/schema/auth";
import { env } from "@kk/env/server";
import { createFileRoute } from "@tanstack/react-router";
import { and, eq } from "drizzle-orm";

// Minimal CF Queue binding shape used to enqueue emails
type EmailQueue = {
	send(
		message: EmailMessage,
		options?: { contentType?: string },
	): Promise<void>;
};

// Mollie payment object (relevant fields only)
interface MolliePayment {
	id: string;
	status: string;
	amount: { value: string; currency: string };
	customerId?: string;
	metadata?: {
		registration_token?: string;
		type?: string;
		drinkkaartId?: string;
		userId?: string;
	};
}

async function fetchMolliePayment(paymentId: string): Promise<MolliePayment> {
	const response = await fetch(
		`https://api.mollie.com/v2/payments/${paymentId}`,
		{
			headers: {
				Authorization: `Bearer ${env.MOLLIE_API_KEY}`,
			},
		},
	);

	if (!response.ok) {
		throw new Error(
			`Failed to fetch Mollie payment ${paymentId}: ${response.status}`,
		);
	}

	return response.json() as Promise<MolliePayment>;
}

async function handleWebhook({
	request,
	context,
}: {
	request: Request;
	context?: unknown;
}) {
	const emailQueue = (context as { emailQueue?: EmailQueue } | undefined)
		?.emailQueue;
	if (!env.MOLLIE_API_KEY) {
		console.error("MOLLIE_API_KEY not configured");
		return new Response("Payment provider not configured", { status: 500 });
	}

	// Mollie sends application/x-www-form-urlencoded with a single "id" field
	let paymentId: string | null = null;
	try {
		const body = await request.text();
		const params = new URLSearchParams(body);
		paymentId = params.get("id");
	} catch {
		return new Response("Invalid request body", { status: 400 });
	}

	if (!paymentId) {
		return new Response("Missing payment id", { status: 400 });
	}

	// Fetch-to-verify: retrieve the actual payment from Mollie to confirm its
	// status. A malicious webhook cannot fake a paid status this way.
	let payment: MolliePayment;
	try {
		payment = await fetchMolliePayment(paymentId);
	} catch (err) {
		console.error("Failed to fetch Mollie payment:", err);
		return new Response("Failed to fetch payment", { status: 500 });
	}

	// Only process paid payments
	if (payment.status !== "paid") {
		return new Response("Payment status ignored", { status: 200 });
	}

	const metadata = payment.metadata;

	try {
		// -------------------------------------------------------------------------
		// Branch: Drinkkaart top-up
		// -------------------------------------------------------------------------
		if (metadata?.type === "drinkkaart_topup") {
			const { drinkkaartId, userId } = metadata;
			if (!drinkkaartId || !userId) {
				console.error(
					"Missing drinkkaartId or userId in drinkkaart_topup payment metadata",
				);
				return new Response("Missing drinkkaart data", { status: 400 });
			}

			// Amount in cents — Mollie returns e.g. "10.00"; parse to integer cents
			const amountCents = Math.round(
				Number.parseFloat(payment.amount.value) * 100,
			);

			// Idempotency: skip if already processed
			const existing = await db
				.select({ id: drinkkaartTopup.id })
				.from(drinkkaartTopup)
				.where(eq(drinkkaartTopup.molliePaymentId, payment.id))
				.limit(1)
				.then((r) => r[0]);

			if (existing) {
				console.log(
					`Drinkkaart topup already processed for payment ${payment.id}`,
				);
				return new Response("OK", { status: 200 });
			}

			const card = await db
				.select()
				.from(drinkkaart)
				.where(eq(drinkkaart.id, drinkkaartId))
				.limit(1)
				.then((r) => r[0]);

			if (!card) {
				console.error(`Drinkkaart not found: ${drinkkaartId}`);
				return new Response("Not found", { status: 404 });
			}

			const balanceBefore = card.balance;
			const balanceAfter = balanceBefore + amountCents;

			// Optimistic lock update
			const result = await db
				.update(drinkkaart)
				.set({
					balance: balanceAfter,
					version: card.version + 1,
					updatedAt: new Date(),
				})
				.where(
					and(
						eq(drinkkaart.id, drinkkaartId),
						eq(drinkkaart.version, card.version),
					),
				);

			if (result.rowsAffected === 0) {
				// Return 500 so Mollie retries; idempotency check prevents double-credit
				console.error(
					`Drinkkaart optimistic lock conflict for ${drinkkaartId}`,
				);
				return new Response("Conflict — please retry", { status: 500 });
			}

			await db.insert(drinkkaartTopup).values({
				id: randomUUID(),
				drinkkaartId,
				userId,
				amountCents,
				balanceBefore,
				balanceAfter,
				type: "payment",
				molliePaymentId: payment.id,
				adminId: null,
				reason: null,
				paidAt: new Date(),
			});

			console.log(
				`Drinkkaart topup successful: drinkkaart=${drinkkaartId}, amount=${amountCents}c, payment=${payment.id}`,
			);

			return new Response("OK", { status: 200 });
		}

		// -------------------------------------------------------------------------
		// Branch: Registration payment
		// -------------------------------------------------------------------------
		const registrationToken = metadata?.registration_token;
		if (!registrationToken) {
			console.error("No registration token in payment metadata");
			return new Response("Missing registration token", { status: 400 });
		}

		// Fetch the registration row
		const regRow = await db
			.select()
			.from(registration)
			.where(eq(registration.managementToken, registrationToken))
			.limit(1)
			.then((r) => r[0]);

		if (!regRow) {
			console.error(`Registration not found for token ${registrationToken}`);
			return new Response("Registration not found", { status: 404 });
		}

		// Mark the registration as paid
		await db
			.update(registration)
			.set({
				paymentStatus: "paid",
				paymentAmount: 0,
				molliePaymentId: payment.id,
				paidAt: new Date(),
			})
			.where(eq(registration.managementToken, registrationToken));

		console.log(
			`Payment successful for registration ${registrationToken}, payment ${payment.id}`,
		);

		// Send payment confirmation email via queue when available, otherwise direct.
		const confirmMsg: EmailMessage = {
			type: "paymentConfirmation",
			to: regRow.email,
			firstName: regRow.firstName,
			managementToken: regRow.managementToken ?? registrationToken,
			drinkCardValue: regRow.drinkCardValue ?? undefined,
			giftAmount: regRow.giftAmount ?? undefined,
		};
		if (emailQueue) {
			await emailQueue.send(confirmMsg);
		} else {
			sendPaymentConfirmationEmail(confirmMsg).catch((err) =>
				console.error(
					`Failed to send payment confirmation email for ${regRow.email}:`,
					err,
				),
			);
		}

		// If this is a watcher with a drink card value, try to credit their
		// drinkkaart immediately — but only if they already have an account.
		if (
			regRow.registrationType === "watcher" &&
			(regRow.drinkCardValue ?? 0) > 0
		) {
			const accountUser = await db
				.select({ id: user.id })
				.from(user)
				.where(eq(user.email, regRow.email))
				.limit(1)
				.then((r) => r[0]);

			if (accountUser) {
				const creditResult = await creditRegistrationToAccount(
					regRow.email,
					accountUser.id,
				).catch((err) => {
					console.error(
						`Failed to credit drinkkaart for ${regRow.email} after registration payment:`,
						err,
					);
					return null;
				});

				if (creditResult?.credited) {
					console.log(
						`Drinkkaart credited ${creditResult.amountCents}c for user ${accountUser.id} (registration ${registrationToken})`,
					);
				} else if (creditResult) {
					console.log(
						`Drinkkaart credit skipped for ${regRow.email}: ${creditResult.status}`,
					);
				}
			} else {
				console.log(
					`No account for ${regRow.email} — drinkkaart credit deferred until signup`,
				);
			}
		}

		return new Response("OK", { status: 200 });
	} catch (error) {
		console.error("Webhook processing error:", error);
		return new Response("Internal error", { status: 500 });
	}
}

export const Route = createFileRoute("/api/webhook/mollie")({
	server: {
		handlers: {
			POST: handleWebhook,
		},
	},
});
