import { TRPCError } from "@trpc/server";
import { add, format, intervalToDuration } from "date-fns";
import { PaymentMethodToString, type PaymentType } from "~/i18n/stripe";
import { parseDBComune, parseDBNazione } from "~/lib/catasto";
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
import type {
	Prezziario,
	PrezziarioIdprezziario,
} from "~/schemas/public/Prezziario";
import type { RinnoviId } from "~/schemas/public/Rinnovi";
import type { ServizioServizioId } from "~/schemas/public/Servizio";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { getComuni, getNazioni } from "~/server/controllers/catasto.controller";
import type { RicevutaProps } from "~/server/services/typst.service";
import type { Result } from "~/utils/result";
import { db } from "../db";
import { createOrdine } from "../services/ordini.service";
import {
	CONDIZIONI_DEFAULT,
	getPrezziarioByIdHandler,
} from "../services/prezziario.service";
import { getServizioById } from "../services/servizio.service";

export type PreparedPaymentData = {
	ordine_id: OrdiniOrdineId;
	packid: PrezziarioIdprezziario;
	amount_cent: number;
	sconto: number;
	packName: string;
	packDesc: string;
	condizioni: string;
};
type preparePaymentInput =
	| {
			servizioId: ServizioServizioId;
			type: Exclude<OrderTypeEnum, OrderTypeEnum.Rinnovo>;
	  }
	| {
			rinnovoId: RinnoviId;
			type: OrderTypeEnum.Rinnovo;
	  };

export type PreparePaymentError =
	| { _tag: "PrezziarioNotFound"; message: string }
	| { _tag: "AlreadyPaid"; message: string }
	| { _tag: "InvalidInput"; message: string }
	| { _tag: "InternalError"; message: string };
export const preparePayment = async (
	props: preparePaymentInput,
): Promise<Result<PreparedPaymentData, PreparePaymentError>> => {
	try {
		switch (props.type) {
			case OrderTypeEnum.Acconto:
			case OrderTypeEnum.Saldo:
			case OrderTypeEnum.Consulenza:
			case OrderTypeEnum.Altro: {
				const { servizioId, type } = props;
				const servizio = await getServizioById(servizioId);
				const inactiveOrders = await db
					.selectFrom("ordini")
					.selectAll()
					.where("servizio_id", "=", servizioId)
					.where("isActive", "=", false)
					.where("paymentstatus", "is", null)
					.execute();

				switch (type) {
					case OrderTypeEnum.Acconto: {
						if (!servizio.isOkAcconto) {
							const acconto = await AccontoSolver(servizio.tipologia);
							if (!acconto.success) {
								return {
									ok: false,
									error: {
										_tag: "PrezziarioNotFound",
										message: `Acconto non trovato per il servizio: ${acconto.message}`,
									},
								};
							}

							// servizio sta aspettando attivazione acconto

							const preExistingAcconto = inactiveOrders.find(
								(ordine) => ordine.type === OrderTypeEnum.Acconto,
							);

							if (preExistingAcconto) {
								// acconto già creato, procedere con questo
								return {
									ok: true,
									value: {
										ordine_id: preExistingAcconto.ordine_id,
										packid: preExistingAcconto.packid,
										amount_cent: preExistingAcconto.amount_cent,
										sconto: preExistingAcconto.sconto,
										packName: acconto.pack.nome_it,
										packDesc: acconto.pack.desc_it,
										condizioni:
											acconto.pack.testo_condizioni || CONDIZIONI_DEFAULT,
									},
								};
							}
							// nessun acconto preesistente, crearne uno nuovo

							const ordine = await createOrdine({
								servizio_id: servizioId,
								amount_cent: acconto.pack.prezzo_cent,
								packid: acconto.pack.idprezziario,
								type: OrderTypeEnum.Acconto,
								userid: servizio.user_id,
							});
							return {
								ok: true,
								value: {
									ordine_id: ordine.ordine_id,
									packid: acconto.pack.idprezziario,
									amount_cent: acconto.pack.prezzo_cent,
									sconto: 0,
									packName: acconto.pack.nome_it,
									packDesc: acconto.pack.desc_it,
									condizioni:
										acconto.pack.testo_condizioni || CONDIZIONI_DEFAULT,
								},
							};
						}
						return {
							ok: false,
							error: {
								_tag: "AlreadyPaid",
								message:
									"Il servizio ha già un acconto attivo, non è possibile preparare un nuovo pagamento",
							},
						};
					}
					case OrderTypeEnum.Saldo: {
						if (!servizio.isOkAcconto) {
							return {
								ok: false,
								error: {
									_tag: "InvalidInput",
									message:
										"Non è possibile procedere con il pagamento del saldo prima di aver attivato l'acconto",
								},
							};
						}
						if (!servizio.isOkSaldo) {
							//find saldo pack based on servizio tipologia and permanenza/budget, if tipologia posizione
							const saldo = await SaldoSolver({
								tipologia: servizio.tipologia,
								permanenza: servizio.permanenza,
								budget: servizio.budget,
							});
							if (!saldo.success) {
								return {
									ok: false,
									error: {
										_tag: "PrezziarioNotFound",
										message: `Saldo non trovato per il servizio: ${saldo.message}`,
									},
								};
							}

							const preExistingSaldo = inactiveOrders.find(
								(ordine) => ordine.type === OrderTypeEnum.Saldo,
							);
							if (preExistingSaldo) {
								// saldo già creato, procedere con questo
								return {
									ok: true,
									value: {
										ordine_id: preExistingSaldo.ordine_id,
										packid: preExistingSaldo.packid,
										amount_cent: preExistingSaldo.amount_cent,
										sconto: preExistingSaldo.sconto,
										packName: saldo.pack.nome_it,
										packDesc: saldo.pack.desc_it,
										condizioni:
											saldo.pack.testo_condizioni || CONDIZIONI_DEFAULT,
									},
								};
							}
							// nessun saldo preesistente, crearne uno nuovo
							const ordine = await createOrdine({
								servizio_id: servizioId,
								amount_cent: saldo.pack.prezzo_cent,
								packid: saldo.pack.idprezziario,
								type: OrderTypeEnum.Saldo,
								userid: servizio.user_id,
							});
							return {
								ok: true,
								value: {
									ordine_id: ordine.ordine_id,
									packid: saldo.pack.idprezziario,
									amount_cent: saldo.pack.prezzo_cent,
									sconto: 0,
									packName: saldo.pack.nome_it,
									packDesc: saldo.pack.desc_it,
									condizioni: saldo.pack.testo_condizioni || CONDIZIONI_DEFAULT,
								},
							};
						}
						return {
							ok: false,
							error: {
								_tag: "AlreadyPaid",
								message:
									"Il servizio ha già un saldo attivo, non è possibile preparare un nuovo pagamento",
							},
						};
					}

					case OrderTypeEnum.Consulenza: {
						if (!servizio.isOkAcconto || !servizio.isOkSaldo) {
							return {
								ok: false,
								error: {
									_tag: "InvalidInput",
									message:
										"Non è possibile procedere con il pagamento della consulenza prima di aver attivato acconto e saldo",
								},
							};
						}
						if (!servizio.isOkConsulenza) {
							const existingConsulenza = inactiveOrders.find(
								(ordine) => ordine.type === OrderTypeEnum.Consulenza,
							);
							if (existingConsulenza) {
								// consulenza già creato, procedere con questo
								const consulenzaPack = await getPrezziarioByIdHandler(
									existingConsulenza.packid,
								);
								if (!consulenzaPack) {
									return {
										ok: false,
										error: {
											_tag: "PrezziarioNotFound",
											message: `Pack della consulenza non trovato: ${existingConsulenza.packid}`,
										},
									};
								}

								return {
									ok: true,
									value: {
										ordine_id: existingConsulenza.ordine_id,
										packid: existingConsulenza.packid,
										amount_cent: existingConsulenza.amount_cent,
										sconto: existingConsulenza.sconto,
										packName: consulenzaPack.nome_it,
										packDesc: consulenzaPack.desc_it,
										condizioni:
											consulenzaPack.testo_condizioni || CONDIZIONI_DEFAULT,
									},
								};
							}

							return {
								ok: false,
								error: {
									_tag: "InvalidInput",
									message:
										"Il servizio non ha un pacchetto di consulenza, è necessario inserirlo prima di procedere",
								},
							};
						}

						return {
							ok: false,
							error: {
								_tag: "AlreadyPaid",
								message:
									"Il servizio ha già una consulenza attiva, non è possibile preparare un nuovo pagamento",
							},
						};
					}
					case OrderTypeEnum.Altro: {
						const existingAltro = inactiveOrders.find(
							(ordine) => ordine.type === OrderTypeEnum.Altro,
						);
						if (existingAltro) {
							// altro già creato, procedere con questo
							const altroPack = await getPrezziarioByIdHandler(
								existingAltro.packid,
							);
							if (!altroPack) {
								return {
									ok: false,
									error: {
										_tag: "PrezziarioNotFound",
										message: `Pack dell'altro non trovato: ${existingAltro.packid}`,
									},
								};
							}

							return {
								ok: true,
								value: {
									ordine_id: existingAltro.ordine_id,
									packid: existingAltro.packid,
									amount_cent: existingAltro.amount_cent,
									sconto: existingAltro.sconto,
									packName: altroPack.nome_it,
									packDesc: altroPack.desc_it,
									condizioni: altroPack.testo_condizioni || CONDIZIONI_DEFAULT,
								},
							};
						}

						return {
							ok: false,
							error: {
								_tag: "InvalidInput",
								message:
									"La preparazione del pagamento per questa tipologia non è supportata",
							},
						};
					}
					default:
						throw new Error(
							`Tipo di ordine non valido: ${type satisfies never}`,
						);
				}
			}
			case OrderTypeEnum.Rinnovo: {
				const { rinnovoId } = props;

				const ordiniRinnovo = await db
					.selectFrom("ordini")
					.selectAll()
					.where("rinnovo_id", "=", rinnovoId)
					.where("type", "=", OrderTypeEnum.Rinnovo)
					.execute();

				const alreadyPaid = ordiniRinnovo.some((ordine) => ordine.isActive);
				if (alreadyPaid) {
					return {
						ok: false,
						error: {
							_tag: "AlreadyPaid",
							message:
								"Il rinnovo ha già un ordine attivo, non è possibile preparare un nuovo pagamento",
						},
					};
				}

				const unusedOrdine = ordiniRinnovo.find((ordine) => !ordine.isActive);
				if (unusedOrdine) {
					// rinnovo già creato, procedere con questo
					const rinnovoPack = await getPrezziarioByIdHandler(
						unusedOrdine.packid,
					);
					if (!rinnovoPack) {
						return {
							ok: false,
							error: {
								_tag: "PrezziarioNotFound",
								message: `Pack del rinnovo non trovato: ${unusedOrdine.packid}`,
							},
						};
					}

					return {
						ok: true,
						value: {
							ordine_id: unusedOrdine.ordine_id,
							packid: unusedOrdine.packid,
							amount_cent: unusedOrdine.amount_cent,
							sconto: unusedOrdine.sconto,
							packName: rinnovoPack.nome_it,
							packDesc: rinnovoPack.desc_it,
							condizioni: rinnovoPack.testo_condizioni || CONDIZIONI_DEFAULT,
						},
					};
				}
				// nessun rinnovo preesistente, crearne uno nuovo
				const rinnovo = await db
					.selectFrom("rinnovi")
					.selectAll()
					.where("id", "=", rinnovoId)
					.executeTakeFirstOrThrow(
						() => new Error(`Rinnovo non trovato - id: ${rinnovoId}`),
					);
				const pack = await RinnovoSolver(rinnovo.decorrenza, rinnovo.scadenza);
				if (!pack.success) {
					return {
						ok: false,
						error: {
							_tag: "PrezziarioNotFound",
							message: `Pack non trovato per il rinnovo: ${pack.message}`,
						},
					};
				}

				const ordine = await createOrdine({
					rinnovo_id: rinnovoId,
					amount_cent: pack.pack.prezzo_cent,
					packid: pack.pack.idprezziario,
					type: OrderTypeEnum.Rinnovo,
					userid: rinnovo.userId,
				});
				return {
					ok: true,
					value: {
						ordine_id: ordine.ordine_id,
						packid: pack.pack.idprezziario,
						amount_cent: pack.pack.prezzo_cent,
						sconto: 0,
						packName: pack.pack.nome_it,
						packDesc: pack.pack.desc_it,
						condizioni: pack.pack.testo_condizioni || CONDIZIONI_DEFAULT,
					},
				};
			}
		}
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore durante la preparazione del pagamento: ${(e as Error).message}`,
		});
	}
};

export const getPagamentoDataForCheckout = async (ordineId: OrdiniOrdineId) => {
	try {
		return await db
			.selectFrom("ordini")
			.select([
				"ordini.ordine_id",
				"ordini.packid",
				"ordini.amount_cent",
				"ordini.servizio_id",
				"ordini.userid",
				"ordini.sconto",
			])
			.innerJoin("users", "users.id", "ordini.userid")
			.select(["users.username", "users.email"])
			.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
			.select(["prezziario.nome_it", "prezziario.desc_it"])
			.where("ordini.ordine_id", "=", ordineId)
			.executeTakeFirstOrThrow(
				() => new Error(`Payment not found - ordineId: ${ordineId}`),
			);
	} catch (e) {
		throw new TRPCError({
			code: "NOT_FOUND",
			message: `Payment not found: ${(e as Error).message}`,
		});
	}
};

// const SaldoTransitorioMapping: Record<number, PrezziarioIdprezziario> = {
// 	0: "SALDOBD_1MESE" as PrezziarioIdprezziario,
// 	1: "SALDOBD_3MESI" as PrezziarioIdprezziario,
// 	2: "SALDOBD_6MESI" as PrezziarioIdprezziario,
// 	3: "SALDOBD_9MESI" as PrezziarioIdprezziario,
// 	4: "SALDOBD_12MESI" as PrezziarioIdprezziario,
// };

// const ConsulenzaPriceMapping = {
// 	"4 + 4": "CONS_4+4" as PrezziarioIdprezziario,
// 	"30 Giorni": "CONS_30GG" as PrezziarioIdprezziario,
// 	Agevolato: "CONS_3+2" as PrezziarioIdprezziario,
// 	Commerciale: "CONS_COMM" as PrezziarioIdprezziario,
// 	Foresteria: "CONS_COMM" as PrezziarioIdprezziario,
// 	"Transi.Age": "CONS_3+2" as PrezziarioIdprezziario,
// 	Transitorio: "CONS_TRAN" as PrezziarioIdprezziario,
// };

type GetPackError = { success: false; message: string };
type GetPackResult = { success: true; pack: Prezziario };

type ServizioPacks = {
	acconto: GetPackResult | GetPackError;
	saldo: GetPackResult | GetPackError;
};

export const AccontoSolver = async (
	tipologia: TipologiaPosizioneEnum,
): Promise<GetPackResult | GetPackError> => {
	switch (tipologia) {
		case TipologiaPosizioneEnum.Transitorio: {
			const pack = await db
				.selectFrom("prezziario")
				.selectAll()
				.where("isActive", "=", true)
				.where("order_type", "=", OrderTypeEnum.Acconto)
				.where("service_type", "=", TipologiaPosizioneEnum.Transitorio)
				.limit(1)
				.executeTakeFirst();

			if (!pack) {
				return { success: false, message: "Pack non trovato" };
			}
			return {
				success: true,
				pack,
			};
		}

		case TipologiaPosizioneEnum.Stabile: {
			const pack = await db
				.selectFrom("prezziario")
				.selectAll()
				.where("isActive", "=", true)
				.where("order_type", "=", OrderTypeEnum.Acconto)
				.where("service_type", "=", TipologiaPosizioneEnum.Stabile)
				.limit(1)
				.executeTakeFirst();

			if (!pack) {
				return { success: false, message: "Pack non trovato" };
			}
			return {
				success: true,
				pack,
			};
		}
		default:
			return { success: false, message: "Seleziona tipologia" };
	}
};

export const SaldoSolver = async ({
	tipologia,
	permanenza,
	budget,
}: {
	tipologia: TipologiaPosizioneEnum;
	permanenza: number | null;
	budget: number;
}): Promise<GetPackResult | GetPackError> => {
	switch (tipologia) {
		case TipologiaPosizioneEnum.Transitorio: {
			if (permanenza === null) {
				return {
					success: false,
					message: "Permanenza non selezionata",
				};
			}

			const pack = await db
				.selectFrom("prezziario")
				.selectAll()
				.where("isActive", "=", true)
				.where("order_type", "=", OrderTypeEnum.Saldo)
				.where("service_type", "=", TipologiaPosizioneEnum.Transitorio)
				.where("service_variant", "=", permanenza)
				.limit(1)
				.executeTakeFirst();

			if (!pack) {
				return { success: false, message: "Pack non trovato" };
			}

			return { success: true, pack };
		}
		case TipologiaPosizioneEnum.Stabile: {
			const budget_step = budget > 700 ? 2 : budget > 500 ? 1 : 0;
			const pack = await db
				.selectFrom("prezziario")
				.selectAll()
				.where("isActive", "=", true)
				.where("order_type", "=", OrderTypeEnum.Saldo)
				.where("service_type", "=", TipologiaPosizioneEnum.Stabile)
				.where("service_variant", "=", budget_step)
				.limit(1)
				.executeTakeFirst();

			if (!pack) {
				return { success: false, message: "Pack non trovato" };
			}
			return {
				success: true,
				pack,
			};
		}
		default:
			return { success: false, message: "Seleziona tipologia" };
	}
};
function rinnovoPermanenzaVariant(permanenza: number): number {
	if (permanenza <= 1) return 0;
	if (permanenza <= 3) return 1;
	if (permanenza <= 6) return 2;
	if (permanenza <= 9) return 3;
	return 4;
}

const rinnovoDurationToPermanenza = (decorrenza: Date, scadenza: Date) => {
	const dur = intervalToDuration({
		start: new Date(decorrenza),
		end: add(new Date(scadenza), { days: 1 }),
	});
	return (dur.years || 0) * 12 + (dur.months || 0) + (dur.days ? 1 : 0);
};

export const RinnovoSolver = async (
	decorrenza: Date,
	scadenza: Date,
): Promise<GetPackResult | GetPackError> => {
	try {
		const permanenza = rinnovoDurationToPermanenza(decorrenza, scadenza);

		const pack = await db
			.selectFrom("prezziario")
			.selectAll()
			.where("isActive", "=", true)
			.where("order_type", "=", OrderTypeEnum.Rinnovo)
			.where("service_type", "=", TipologiaPosizioneEnum.Transitorio)
			.where("service_variant", "=", rinnovoPermanenzaVariant(permanenza))
			.limit(1)
			.executeTakeFirst();

		if (!pack) {
			return { success: false, message: "Pack non trovato" };
		}

		return { success: true, pack };
	} catch (error) {
		return {
			success: false,
			message: `Errore nel calcolo del rinnovo: ${(error as Error).message}`,
		};
	}
};

export const getPacksPerServizio = async (
	servizioId: ServizioServizioId,
): Promise<ServizioPacks> => {
	const servizio = await getServizioById(servizioId);
	if (!servizio) {
		throw new TRPCError({
			code: "NOT_FOUND",
			message: `Servizio non trovato - servizioId: ${servizioId}`,
		});
	}
	return {
		acconto: await AccontoSolver(servizio.tipologia),
		saldo: await SaldoSolver({
			tipologia: servizio.tipologia,
			permanenza: servizio.permanenza,
			budget: servizio.budget,
		}),
	};
};

export const previewSaldo = async (
	servizioId: ServizioServizioId,
): Promise<{ success: false; message: string } | GetPackResult> => {
	const servizio = await getServizioById(servizioId);
	if (!servizio) {
		return {
			success: false,
			message: `Servizio non trovato - servizioId: ${servizioId}`,
		};
	}
	const saldo = await SaldoSolver({
		tipologia: servizio.tipologia,
		permanenza: servizio.permanenza,
		budget: servizio.budget,
	});

	return saldo;
};

export const getRicevutaData = async (
	ordineId: OrdiniOrdineId,
): Promise<{ title: string; data: RicevutaProps }> => {
	try {
		const ordine = await db
			.selectFrom("ordini")
			.select([
				"ordini.ordine_id",
				"ordini.userid",
				"ordini.amount_cent",
				"ordini.sconto",
				"ordini.created_at",
				"ordini.paymentmethod",
				"ordini.type",
				"ordini.servizio_id",
			])
			.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
			.select("prezziario.nome_it")
			.where("ordini.ordine_id", "=", ordineId)
			.executeTakeFirstOrThrow(
				() => new Error(`Ordine not found - ordineId: ${ordineId}`),
			);

		const anagrafica = await db
			.selectFrom("users")
			.select("users.username")
			.innerJoin("users_anagrafica", "users_anagrafica.userid", "users.id")
			.select([
				"users_anagrafica.via_residenza",
				"users_anagrafica.civico_residenza",
				"users_anagrafica.comune_residenza",
				"users_anagrafica.provincia_residenza",
				"users_anagrafica.cap_residenza",
				"users_anagrafica.nazione_residenza",
				"users_anagrafica.codice_fiscale",
			])
			.where("users.id", "=", ordine.userid)
			.executeTakeFirstOrThrow(
				() => new Error(`Anagrafica not found - userid: ${ordine.userid}`),
			);
		const comuni = await getComuni();
		const nazioni = await getNazioni();

		const parsedComune = parseDBComune(anagrafica.comune_residenza, comuni);
		const parsedNazione = parseDBNazione(anagrafica.nazione_residenza, nazioni);

		let previewSaldo: Prezziario | null = null;

		if (ordine.type === OrderTypeEnum.Acconto && ordine.servizio_id) {
			const servizio = await getServizioById(ordine.servizio_id);
			if (servizio) {
				const saldo = await SaldoSolver({
					tipologia: servizio.tipologia,
					permanenza: servizio.permanenza,
					budget: servizio.budget,
				});
				if (saldo.success) {
					previewSaldo = saldo.pack;
				} else {
					console.error(
						`Errore nel calcolo del saldo per il servizio ${ordine.servizio_id}: ${saldo.message}, procedura getRicevutaData`,
					);
				}
			} else {
				console.error(
					`Servizio non trovato per ordine ${ordineId} con servizio_id ${ordine.servizio_id}, procedura getRicevutaData`,
				);
			}
		}
		if (!anagrafica.codice_fiscale) {
			throw new Error(`Codice fiscale mancante per utente ${ordine.userid}`);
		}

		//console.log(previewSaldo);

		const data = {
			ordineId: ordine.ordine_id,
			datetime: format(ordine.created_at, "HH:mm"),
			date: format(ordine.created_at, "dd/MM/yyyy"),
			nominativo: anagrafica.username,
			codicefiscale: anagrafica.codice_fiscale,
			indirizzo: `${anagrafica.via_residenza} ${anagrafica.civico_residenza}\n${parsedComune?.nome} (${anagrafica.provincia_residenza}) ${anagrafica.cap_residenza}`,
			nazione: parsedNazione?.nome || "N/A",
			method: PaymentMethodToString(
				(ordine.paymentmethod || "manual") as PaymentType,
			),
			items: [
				{
					title: ordine.nome_it,
					amount: ordine.amount_cent,
					discount: ordine.sconto,
					strike: false,
					desc: null,
				},
				...(previewSaldo
					? [
							{
								title: previewSaldo.nome_it,
								amount: previewSaldo.prezzo_cent,
								discount: null,
								strike: true,
								desc: "Pagamento differito, non è dovuto in questo momento",
							},
						]
					: []),
			],
			total: ordine.amount_cent - ordine.sconto,
			vat: (ordine.amount_cent - ordine.sconto) * 0.22,
		};
		return {
			title: `ricevuta-cortesia-${anagrafica.username.replace(/\s+/g, "-")}-${format(ordine.created_at, "dd-MM-yyyy")}.pdf`,
			data,
		};
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error fetching ordine ricevuta data: ${(e as Error).message}`,
		});
	}
};
