import { round } from "$lib/core/num.utils";
import {
	getCKUseCases,
	type CheckoutFlowUseCases,
} from "$lib/domains/ckflow/domain/usecases";
import { and, arrayContains, eq, or, type Database } from "@pkg/db";
import { flightTicketInfo, order } from "@pkg/db/schema";
import { getError, Logger } from "@pkg/logger";
import { ERROR_CODES, type Result } from "@pkg/result";
import {
	flightPriceDetailsModel,
	flightTicketModel,
	TicketType,
	type FlightPriceDetails,
	type FlightTicket,
	type TicketSearchDTO,
} from "./entities";
import type { ScrapedTicketsDataSource } from "./scrape.data.source";

export class TicketRepository {
	private db: Database;
	private ckUseCases: CheckoutFlowUseCases;
	private scraper: ScrapedTicketsDataSource;

	constructor(db: Database, scraper: ScrapedTicketsDataSource) {
		this.db = db;
		this.scraper = scraper;
		this.ckUseCases = getCKUseCases();
	}

	async getCheckoutUrlByRefOId(refOId: number): Promise<Result<string>> {
		try {
			const _o = await this.db.query.order.findFirst({
				where: eq(order.id, refOId),
				with: {
					flightTicketInfo: {
						columns: { id: true, checkoutUrl: true },
					},
				},
			});
			const chktUrl = _o?.flightTicketInfo?.checkoutUrl;
			console.log(_o);
			return { data: chktUrl };
		} catch (e) {
			return {
				error: getError(
					{
						code: ERROR_CODES.NOT_FOUND_ERROR,
						message: "Failed to fetch ticket url",
						userHint: "Please try again later",
						detail: "An error occurred while fetching ticket url",
					},
					e,
				),
			};
		}
	}

	async getTicketIdbyRefOid(refOid: number): Promise<Result<number>> {
		try {
			const out = await this.db.query.flightTicketInfo.findFirst({
				where: arrayContains(flightTicketInfo.refOIds, [refOid]),
				columns: { id: true },
			});
			if (out && out.id) {
				return { data: out?.id };
			}
			return {};
		} catch (e) {
			Logger.debug(refOid);
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to lookup ticket",
						detail: "A database error occured while getting ticket by id",
						userHint: "Contact us to resolve this issue",
						actionable: false,
					},
					e,
				),
			};
		}
	}

	async searchForTickets(
		payload: TicketSearchDTO,
	): Promise<Result<FlightTicket[]>> {
		try {
			let out = await this.scraper.searchForTickets(payload);
			if (out.error || (out.data && out.data.length < 1)) {
				return out;
			}
			return out;
		} catch (err) {
			return {
				error: getError(
					{
						code: ERROR_CODES.NETWORK_ERROR,
						message: "Failed to search for tickets",
						detail: "Could not fetch tickets for the given payload",
						userHint: "Please try again later",
						error: err,
						actionable: false,
					},
					err,
				),
			};
		}
	}

	async getTicketById(id: number): Promise<Result<FlightTicket>> {
		const out = await this.db.query.flightTicketInfo.findFirst({
			where: eq(flightTicketInfo.id, id),
		});
		if (!out) {
			return {
				error: getError({
					code: ERROR_CODES.NOT_FOUND_ERROR,
					message: "Ticket not found",
					userHint:
						"Please check if the selected ticket is correct, or try again",
					detail: "The ticket is not found by the id provided",
				}),
			};
		}
		const parsed = flightTicketModel.safeParse(out);
		if (!parsed.success) {
			return {
				error: getError(
					{
						code: ERROR_CODES.INTERNAL_SERVER_ERROR,
						message: "Failed to parse ticket",
						userHint: "Please try again",
						detail: "Failed to parse ticket",
					},
					JSON.stringify(parsed.error.errors),
				),
			};
		}
		return { data: parsed.data };
	}

	async getTicketIdByInfo(info: {
		ticketId: string;
		arrival: string;
		departure: string;
		cabinClass: string;
		departureDate: string;
		returnDate: string;
	}): Promise<Result<number>> {
		try {
			const out = await this.db.query.flightTicketInfo.findFirst({
				where: or(
					eq(flightTicketInfo.ticketId, info.ticketId),
					and(
						eq(flightTicketInfo.arrival, info.arrival),
						eq(flightTicketInfo.departure, info.departure),
						eq(flightTicketInfo.cabinClass, info.cabinClass),
						eq(
							flightTicketInfo.departureDate,
							new Date(info.departureDate),
						),
					),
				),
				columns: { id: true },
			});
			if (out && out.id) {
				return { data: out?.id };
			}

			return {};
		} catch (e) {
			Logger.debug(info);
			return {
				error: getError(
					{
						code: ERROR_CODES.DATABASE_ERROR,
						message: "Failed to lookup ticket",
						detail: "A database error occured while getting ticket by id",
						userHint: "Contact us to resolve this issue",
						actionable: false,
					},
					e,
				),
			};
		}
	}

	async createTicket(
		payload: FlightTicket,
		isCache = false,
	): Promise<Result<number>> {
		try {
			let rd = new Date();
			if (
				payload.returnDate &&
				payload.returnDate.length > 0 &&
				payload.flightType !== TicketType.OneWay
			) {
				rd = new Date(payload.returnDate);
			}
			const out = await this.db
				.insert(flightTicketInfo)
				.values({
					ticketId: payload.ticketId,
					flightType: payload.flightType,

					arrival: payload.arrival,
					departure: payload.departure,
					cabinClass: payload.cabinClass,
					departureDate: new Date(payload.departureDate),
					returnDate: rd,

					priceDetails: payload.priceDetails,
					passengerCounts: payload.passengerCounts,
					bagsInfo: payload.bagsInfo,

					lastAvailable: payload.lastAvailable,

					flightIteneraries: payload.flightIteneraries,

					dates: payload.dates,

					checkoutUrl: payload.checkoutUrl ?? "",
					refOIds: payload.refOIds ?? [],

					refundable: payload.refundable ?? false,
					isCache: isCache ? true : (payload.isCache ?? isCache),

					shareId: payload.shareId,
				})
				.returning({ id: flightTicketInfo.id })
				.execute();

			if (!out || out.length === 0) {
				Logger.error("Failed to create ticket");
				Logger.debug(out);
				Logger.debug(payload);
				return {
					error: getError({
						code: ERROR_CODES.INTERNAL_SERVER_ERROR,
						message: "Failed to create ticket",
						userHint: "Please try again",
						detail: "Failed to create ticket",
					}),
				};
			}

			return { data: out[0].id };
		} catch (e) {
			return {
				error: getError(
					{
						code: ERROR_CODES.INTERNAL_SERVER_ERROR,
						message: "An error occured while creating ticket",
						userHint: "Please try again later",
						actionable: false,
						detail: "An error occured while creating ticket",
					},
					e,
				),
			};
		}
	}

	async updateTicketPrices(
		tid: number,
		payload: FlightPriceDetails,
	): Promise<Result<FlightPriceDetails>> {
		const cond = eq(flightTicketInfo.id, tid);
		const currInfo = await this.db.query.flightTicketInfo.findFirst({
			where: cond,
			columns: { priceDetails: true },
		});
		if (!currInfo) {
			return {
				error: getError({
					code: ERROR_CODES.NOT_FOUND_ERROR,
					message: "Could not find ticket",
					userHint: "Please try again later",
					detail: "Could not fin the ticket by the provided id",
				}),
			};
		}
		const info = flightPriceDetailsModel.parse(currInfo.priceDetails);
		Logger.info("Updating the price details from:");
		Logger.debug(info);
		const discountAmt = round(info.basePrice - payload.displayPrice);
		const newInfo = {
			...info,
			discountAmount: discountAmt,
			displayPrice: payload.displayPrice,
		} as FlightPriceDetails;
		Logger.info("... to:");
		Logger.debug(newInfo);
		const out = await this.db
			.update(flightTicketInfo)
			.set({ priceDetails: newInfo })
			.where(cond)
			.execute();
		Logger.info("Updated the price info");
		Logger.debug(out);
		return { data: newInfo };
	}

	async uncacheAndSaveTicket(tid: number): Promise<Result<number>> {
		try {
			const out = await this.db
				.update(flightTicketInfo)
				.set({ isCache: false })
				.where(eq(flightTicketInfo.id, tid))
				.returning({ id: flightTicketInfo.id })
				.execute();

			if (!out || out.length === 0) {
				Logger.error("Failed to uncache ticket");
				Logger.debug(out);
				Logger.debug(tid);
				return {
					error: getError({
						code: ERROR_CODES.INTERNAL_SERVER_ERROR,
						message: "Failed to process ticket at this moment",
						userHint: "Please try again later",
						detail: "Failed to uncache ticket",
					}),
				};
			}

			return { data: out[0].id };
		} catch (e) {
			return {
				error: getError(
					{
						code: ERROR_CODES.INTERNAL_SERVER_ERROR,
						message: "An error occured while uncaching ticket",
						userHint: "Please try again later",
						actionable: false,
						detail: "An error occured while uncaching ticket",
					},
					e,
				),
			};
		}
	}

	async setRefOIdsForTicket(
		tid: number,
		oids: number[],
	): Promise<Result<boolean>> {
		Logger.info(`Setting refOIds(${oids}) for ticket ${tid}`);
		const out = await this.db
			.update(flightTicketInfo)
			.set({ refOIds: oids })
			.where(eq(flightTicketInfo.id, tid))
			.execute();
		return { data: out.length > 0 };
	}

	async areCheckoutAlreadyLiveForOrder(refOids: number[]): Promise<boolean> {
		return this.ckUseCases.areCheckoutAlreadyLiveForOrder(refOids);
	}
}
