import { and, desc, eq, type Database } from "@pkg/db";
import { checkoutFlowSession, product } from "@pkg/db/schema";
import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result";
import {
	CKActionType,
	flowInfoModel,
	type FlowInfo,
	type PendingAction,
} from "./data";

export class CheckoutFlowRepository {
	constructor(private db: Database) {}

	// Common method to parse and enrich flow info with product details
	private async parseFlowInfo(
		sessionData: any,
		includeStaleness = true,
	): Promise<FlowInfo | null> {
		if (!sessionData) {
			return null;
		}

		try {
			// Add data staleness indicators if requested
			let personalInfoStale = false;
			let paymentInfoStale = false;

			if (includeStaleness) {
				const now = new Date();
				const STALE_THRESHOLD_MS = 5 * 60 * 1000; // 5 minutes

				personalInfoStale = sessionData.personalInfoLastSyncedAt
					? now.getTime() -
							sessionData.personalInfoLastSyncedAt.getTime() >
						STALE_THRESHOLD_MS
					: false;

				paymentInfoStale = sessionData.paymentInfoLastSyncedAt
					? now.getTime() -
							sessionData.paymentInfoLastSyncedAt.getTime() >
						STALE_THRESHOLD_MS
					: false;
			}

			// Fetch product info if we have a product ID
			let productInfo = undefined;
			if (sessionData.productId) {
				try {
					const prodResult = await this.db.query.product.findFirst({
						where: eq(product.id, sessionData.productId),
					});
					if (prodResult) {
						productInfo = { ...prodResult };
					}
				} catch (err) {
					Logger.warn(
						"Failed to fetch product info for session detail",
						err,
					);
					// Continue without product info - it's optional
				}
			}

			// Prepare dates for the model
			const flowInfoData = {
				...sessionData,
				productInfo: productInfo,
				productId: sessionData.productId,
				pendingActions: sessionData.pendingActions as any,
				personalInfo: sessionData.personalInfo as any,
				paymentInfo: sessionData.paymentInfo as any,
				refOIds: sessionData.refOIds as any,
				createdAt: sessionData.createdAt.toISOString(),
				lastPinged: sessionData.lastPinged.toISOString(),
				lastSyncedAt: sessionData.lastSyncedAt.toISOString(),
				personalInfoLastSyncedAt: sessionData.personalInfoLastSyncedAt
					? sessionData.personalInfoLastSyncedAt.toISOString()
					: undefined,
				paymentInfoLastSyncedAt: sessionData.paymentInfoLastSyncedAt
					? sessionData.paymentInfoLastSyncedAt.toISOString()
					: undefined,
				completedAt: sessionData.completedAt
					? sessionData.completedAt.toISOString()
					: undefined,
			};

			// Add staleness indicators if requested
			if (includeStaleness) {
				Object.assign(flowInfoData, {
					personalInfoStale,
					paymentInfoStale,
				});
			}

			const parsed = flowInfoModel.safeParse(flowInfoData);
			if (parsed.error) {
				Logger.info(
					`Failed to parse flow info for ${sessionData.flowId}`,
				);
				Logger.error(parsed.error);
				return null;
			}

			return parsed.data;
		} catch (err) {
			Logger.error("Error in parseFlowInfo", err);
			return null;
		}
	}

	async getActiveSessionsList(): Promise<Result<FlowInfo[]>> {
		try {
			const out = await this.db
				.select()
				.from(checkoutFlowSession)
				.where(
					and(
						eq(checkoutFlowSession.isActive, true),
						eq(checkoutFlowSession.isDeleted, false),
					),
				)
				.orderBy(desc(checkoutFlowSession.lastSyncedAt))
				.execute();

			const data = [] as FlowInfo[];

			for (const each of out) {
				if (!each) continue;

				const parsedFlow = await this.parseFlowInfo(each);
				if (!parsedFlow) continue;

				// We don't want any sessions that are under pending termination
				if (
					parsedFlow.pendingActions.find(
						(e) => e.type === CKActionType.TerminateSession,
					)
				) {
					continue;
				}

				data.push(parsedFlow);
			}

			return { data };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to retrieve active sessions",
						userHint: "Please try again later",
						detail: "Error occurred while fetching active sessions from database",
					},
					err,
				),
			};
		}
	}

	async getAllSessionsList(
		includeDeleted: boolean = false,
	): Promise<Result<FlowInfo[]>> {
		try {
			let query: any = undefined;
			if (includeDeleted) {
				query = this.db
					.select()
					.from(checkoutFlowSession)
					.orderBy(desc(checkoutFlowSession.lastSyncedAt));
			} else {
				query = this.db
					.select()
					.from(checkoutFlowSession)
					.where(eq(checkoutFlowSession.isDeleted, false))
					.orderBy(desc(checkoutFlowSession.lastSyncedAt));
			}

			const out = await query.execute();
			const data = [] as FlowInfo[];

			for (const each of out) {
				if (!each) continue;

				const parsedFlow = await this.parseFlowInfo(each);
				if (parsedFlow) {
					data.push(parsedFlow);
				}
			}

			return { data };
		} catch (err) {
			Logger.error("Failed to get all sessions list", err);
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to retrieve sessions",
						userHint: "Please try again later",
						detail: "Error occurred while fetching sessions from database",
					},
					err,
				),
			};
		}
	}

	async getFlowInfo(flowId: string): Promise<Result<FlowInfo>> {
		try {
			const session = await this.db.query.checkoutFlowSession.findFirst({
				where: eq(checkoutFlowSession.flowId, flowId),
			});

			if (!session) {
				return {};
			}

			const parsedFlow = await this.parseFlowInfo(session);
			if (!parsedFlow) {
				return {
					error: getError({
						code: ERROR_CODES.VALIDATION_ERROR,
						message: "Invalid flow info found",
						userHint: "Please restart the checkout process",
						detail: "An error occurred while parsing checkout session info",
					}),
				};
			}

			return { data: parsedFlow };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.API_ERROR,
						message: "Failed to get flow info",
						userHint:
							"Please restart the checkout, or try again later",
						detail: "An error occurred while getting checkout session info",
					},
					err,
				),
			};
		}
	}

	async updateFlowState(
		fid: string,
		state: FlowInfo,
	): Promise<Result<boolean>> {
		const existingSession = await this.db
			.select({ id: checkoutFlowSession.id })
			.from(checkoutFlowSession)
			.where(eq(checkoutFlowSession.flowId, fid));

		if (!existingSession || existingSession.length === 0) {
			return {
				error: getError({
					code: ERROR_CODES.NOT_FOUND_ERROR,
					message: "Session not found",
					userHint: "The session you're trying to update doesn't exist",
					detail: `Flow with ID ${fid} not found in database`,
				}),
			};
		}

		await this.db
			.update(checkoutFlowSession)
			.set({
				checkoutStep: state.checkoutStep,
				showVerification: state.showVerification,
				lastPinged: new Date(),
				isActive: state.isActive,
				lastSyncedAt: new Date(),
				pendingActions: state.pendingActions as any,
				personalInfo: state.personalInfo as any,
				paymentInfo: state.paymentInfo as any,
				otpCode: state.otpCode,
				otpSubmitted: state.otpSubmitted,
				partialOtpCode: state.partialOtpCode,
				reserved: state.reserved ?? false,
				reservedBy: state.reservedBy,
			})
			.where(eq(checkoutFlowSession.flowId, fid))
			.execute();
		return { data: true };
	}

	async addPendingAction(
		flowId: string,
		action: PendingAction,
	): Promise<Result<boolean>> {
		try {
			// First get the current session to access its pendingActions
			const sessionResult = await this.getFlowInfo(flowId);
			if (sessionResult.error || !sessionResult.data) {
				return { error: sessionResult.error };
			}

			const session = sessionResult.data;
			const updatedPendingActions = [...session.pendingActions, action];

			await this.db
				.update(checkoutFlowSession)
				.set({
					pendingActions: updatedPendingActions as any,
					lastSyncedAt: new Date(),
				})
				.where(eq(checkoutFlowSession.flowId, flowId));

			return { data: true };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to add action to session",
						userHint: "Please try again",
						detail: "Error occurred when adding pending action to session",
					},
					err,
				),
			};
		}
	}

	async cleanupFlow(flowId: string): Promise<Result<boolean>> {
		try {
			await this.db
				.update(checkoutFlowSession)
				.set({
					isActive: false,
					completedAt: new Date(),
					sessionOutcome: "TERMINATED",
				})
				.where(eq(checkoutFlowSession.flowId, flowId));
			return { data: true };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to cleanup checkout session",
						userHint: "Please try again later",
						detail: "Error occurred while marking session as inactive in database",
					},
					err,
				),
			};
		}
	}

	async softDeleteFlow(flowId: string): Promise<Result<boolean>> {
		try {
			await this.db
				.update(checkoutFlowSession)
				.set({ isDeleted: true })
				.where(eq(checkoutFlowSession.flowId, flowId));
			return { data: true };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to delete session",
						userHint: "Please try again later",
						detail: "Error occurred while soft-deleting session in database",
					},
					err,
				),
			};
		}
	}

	async restoreFlow(flowId: string): Promise<Result<boolean>> {
		try {
			await this.db
				.update(checkoutFlowSession)
				.set({ isDeleted: false })
				.where(eq(checkoutFlowSession.flowId, flowId));

			return { data: true };
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to restore session",
						userHint: "Please try again later",
						detail: "Error occurred while restoring soft-deleted session in database",
					},
					err,
				),
			};
		}
	}
}
