import { TRPCError } from "@trpc/server";
import { add, isBefore } from "date-fns";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
import { z } from "zod/v4";
import { env } from "~/env";
import { withParsedDates } from "~/lib/dateParserJsonKysely";
import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
import type { Ordini, OrdiniOrdineId } from "~/schemas/public/Ordini";
import type { Prezziario } from "~/schemas/public/Prezziario";
import type {
	Servizio,
	ServizioServizioId,
	ServizioUpdate,
} from "~/schemas/public/Servizio";
import type { ServizioAnnunci } from "~/schemas/public/ServizioAnnunci";
import type { TestiEStringheStingaId } from "~/schemas/public/TestiEStringhe";
import type TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import type { Users, UsersId } from "~/schemas/public/Users";
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
import type { UsersStorageUserStorageId } from "~/schemas/public/UsersStorage";
import {
	addEventToQueue,
	canSendNotification,
	clearUserEmailQueue,
	nextTimeForNotification,
} from "~/server/controllers/event_queue.controller";
import {
	type MinimalSMSData,
	SendMessage,
} from "~/server/controllers/skebby.controller";
import { db } from "~/server/db";
import { NewMail, type NewMailProps } from "~/server/services/mailer";
import { getUser } from "~/server/services/user.service";
import { withImages, withVideos } from "~/utils/kysely-helper";
import { GetUserInterests } from "../services/interests.service";
import { getServizioById } from "../services/servizio.service";
import type { AnnuncioRicerca } from "./annunci.controller";

export const getDataPerAcquisto = async (servizioId: ServizioServizioId) => {
	try {
		const servizio = await getServizioById(servizioId);

		if (!servizio) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: `Servizio not found - servizioId: ${servizioId}`,
			});
		}

		const userData = await db
			.selectFrom("users")
			.select([
				"users.id",
				"users.nome",
				"users.cognome",
				"users.email",
				"users.telefono",
			])
			.where("id", "=", servizio.user_id)
			.executeTakeFirstOrThrow(
				() => new Error(`User not found - userId: ${servizio.user_id}`),
			);

		if (!userData) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: "User not found",
			});
		}
		const anagrafica = await db
			.selectFrom("users_anagrafica")
			.selectAll("users_anagrafica")
			.where("users_anagrafica.userid", "=", servizio.user_id)
			.executeTakeFirst();
		return {
			anagrafica,
			servizio,
			userData,
		};
	} catch (e) {
		throw new TRPCError({
			code: "NOT_FOUND",
			message: `Servizio not found: ${(e as Error).message}`,
		});
	}
};

export const processServizioOnboard = async ({
	servizioId,
	userId,
	user,
	anagrafica,
	servizio,
}: {
	servizioId: ServizioServizioId;
	userId: UsersId;
	user: Pick<Users, "nome" | "cognome" | "email" | "telefono" | "username">;
	anagrafica: UsersAnagraficaUpdate;
	servizio: ServizioUpdate;
}) => {
	try {
		await db.transaction().execute(async (tx) => {
			// Update user and anagrafica data
			await tx
				.updateTable("users")
				.set(user)
				.where("id", "=", userId)
				.execute();

			const { idanagrafica, ...rest } = anagrafica;
			if (idanagrafica) {
				await tx
					.updateTable("users_anagrafica")
					.set({
						...rest,
						userid: userId,
					})
					.where("idanagrafica", "=", idanagrafica)
					.returningAll()
					.executeTakeFirstOrThrow(
						() =>
							new Error(
								`Failed to update users_anagrafica - idanagrafica: ${idanagrafica}`,
							),
					);
			} else {
				await tx
					.insertInto("users_anagrafica")
					.values({
						...rest,
						userid: userId,
					})
					.returningAll()
					.executeTakeFirstOrThrow(
						() => new Error("Failed to insert new users_anagrafica"),
					);
			}
			// Update servizio data
			return await tx
				.updateTable("servizio")
				.set({ ...servizio, onboardOk: true })
				.where("servizio_id", "=", servizioId)
				.returningAll()
				.executeTakeFirstOrThrow(
					() =>
						new Error(`Failed to update servizio - servizioId: ${servizioId}`),
				);
		});

		return true;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error updating data for acquisto: ${(e as Error).message}`,
		});
	}
};

/**
 * Attiva il servizio senza pagamento,\
 * usato se un acconto già attivo è presente per il servizio,\
 * invio delle condizioni contrattuali in allegato.
 */
export const attivaServizio_SaltaPagamentoAcconto = async (
	ordineId: OrdiniOrdineId,
) => {
	try {
		return await db.transaction().execute(async (tx) => {
			const ordine = await tx
				.selectFrom("ordini")
				.innerJoin("users", "users.id", "ordini.userid")
				.innerJoin("prezziario", "prezziario.idprezziario", "ordini.packid")
				.select([
					"ordini.ordine_id",
					"ordini.servizio_id",
					"ordini.created_at",
					"prezziario.testo_condizioni",
					"users.email",
					"users.id as userId",
				])
				.where("ordine_id", "=", ordineId)
				.executeTakeFirstOrThrow(
					() => new Error(`Ordine not found - ordineId: ${ordineId}`),
				);

			const user = await getUser({ db: tx, userId: ordine.userId });

			const servizio = await tx
				.updateTable("servizio")
				.set({
					decorrenza: new Date(),
					isOkAcconto: true,
				})
				.where("servizio_id", "=", ordine.servizio_id)
				.returning(["interruzioneDays"])
				.executeTakeFirstOrThrow(
					() =>
						new Error(
							`Failed to update servizio - servizioId: ${ordine.servizio_id}`,
						),
				);

			if (user.comms_enabled) {
				await addEventToQueue({
					data: {
						tipo: "email",
						data: {
							template: {
								mailType: "servizioAttivato",
							},
							mail: {
								subject: "Pagamento Confermato - Acconto Infoalloggi.it",
								to: ordine.email,
								attachments: ordine.testo_condizioni
									? [
											{
												filename: `condizioni_contrattuali_${ordine.created_at.toISOString()}.pdf`,
												generate: {
													type: "condizioni",
													stringId:
														ordine.testo_condizioni as TestiEStringheStingaId,
												},

												encoding: "base64",
												contentType: "application/pdf",
											},
										]
									: [],
							},
							userId: ordine.userId,
						},
					},
					lock_expires: add(new Date(), { seconds: 30 }),
				});

				await addEventToQueue({
					data: {
						tipo: "email",
						data: {
							template: {
								mailType: "avvisoInterruzione",
							},
							mail: {
								subject: "Avviso Interruzione",
								to: ordine.email,
							},
							userId: ordine.userId,
						},
					},
					lock_expires: add(new Date(), {
						days: servizio.interruzioneDays - 5,
					}),
				});
			}
			return true;
		});
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error activating servizio without payment: ${(e as Error).message}`,
		});
	}
};

export const AdminAttivaServizio_Ufficio = async (
	servizioId: ServizioServizioId,
) => {
	try {
		return await db.transaction().execute(async (tx) => {
			const data = await tx
				.selectFrom("servizio")
				.select(["servizio.user_id", "servizio.interruzioneDays"])
				.innerJoin("users", "users.id", "servizio.user_id")
				.select(["users.email", "users.comms_enabled"])
				.where("servizio_id", "=", servizioId)
				.executeTakeFirstOrThrow(
					() => new Error(`Servizio not found - servizioId: ${servizioId}`),
				);
			const today = new Date();
			await tx
				.updateTable("servizio")
				.set({
					decorrenza: today,
					isOkAcconto: true,
					onboardOk: true,
				})
				.where("servizio_id", "=", servizioId)
				.execute();

			if (data.comms_enabled) {
				await addEventToQueue({
					data: {
						tipo: "email",
						data: {
							template: {
								mailType: "avvisoInterruzione",
							},
							mail: {
								subject: "Avviso Interruzione",
								to: data.email,
							},
							userId: data.user_id,
						},
					},
					lock_expires: add(today, { days: data.interruzioneDays - 5 }), //avviso 5 giorni prima della scadenza del periodo di ripensamento
				});
			}
			return true;
		});
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error activating servizio from office: ${(e as Error).message}`,
		});
	}
};

type DocRef = {
	storageId: string;
	ref: UsersStorageUserStorageId | null;
};

export type ServizioAnnuncioData = ServizioAnnunci &
	Pick<
		Annunci,
		| "id"
		| "codice"
		| "comune"
		| "provincia"
		| "prezzo"
		| "consegna"
		| "numero_camere"
		| "mq"
		| "tipo"
		| "titolo_it"
		| "titolo_en"
		| "desc_en"
		| "desc_it"
		| "modificato_il"
		| "stato"
		| "external_videos"
		| "media_updated_at"
		| "homepage"
		| "web"
		| "disponibile_da"
		| "persone"
		| "permanenza"
	> & {
		images: {
			img: string;
			thumb: string;
		}[];
		videos: {
			thumb: string;
			video: string;
		}[];
	};
export type ServizioData = Servizio & {
	doc_personale_fronte: DocRef | null;
	doc_personale_retro: DocRef | null;
	doc_motivazione: DocRef | null;
	annunci: ServizioAnnuncioData[];
	ordini: (Ordini & Pick<Prezziario, "testo_condizioni">)[];
};

export const getAllServizioAnnunci = async (
	userId: UsersId,
): Promise<ServizioData[]> => {
	try {
		const data = await withParsedDates(
			db
				.selectFrom("servizio")
				.where("user_id", "=", userId)
				.leftJoin(
					"users_anagrafica",
					"servizio.user_id",
					"users_anagrafica.userid",
				)
				.selectAll("servizio")
				.select((eb) => [
					jsonObjectFrom(
						eb
							.selectFrom("users_storage")
							.select([
								"users_storage.storageId as storageId",
								"users_anagrafica.doc_personale_fronte_ref as ref",
							])
							.whereRef(
								"users_storage.user_storage_id",
								"=",
								"users_anagrafica.doc_personale_fronte_ref",
							),
					).as("doc_personale_fronte"),
					jsonObjectFrom(
						eb
							.selectFrom("users_storage")
							.select([
								"users_storage.storageId as storageId",
								"users_anagrafica.doc_personale_retro_ref as ref",
							])
							.whereRef(
								"users_storage.user_storage_id",
								"=",
								"users_anagrafica.doc_personale_retro_ref",
							),
					).as("doc_personale_retro"),
					jsonObjectFrom(
						eb
							.selectFrom("users_storage")
							.select([
								"users_storage.storageId as storageId",
								"servizio.doc_motivazione_ref as ref",
							])
							.whereRef(
								"users_storage.user_storage_id",
								"=",
								"servizio.doc_motivazione_ref",
							),
					).as("doc_motivazione"),
					jsonArrayFrom(
						eb
							.selectFrom("servizio_annunci")
							.selectAll("servizio_annunci")
							.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
							.select((_eb) => [
								"annunci.id",
								"annunci.codice",
								"annunci.comune",
								"annunci.provincia",
								"annunci.prezzo",
								"annunci.consegna",
								"annunci.numero_camere",
								"annunci.mq",
								"annunci.tipo",
								"annunci.titolo_it",
								"annunci.titolo_en",
								"annunci.desc_en",
								"annunci.desc_it",
								"annunci.modificato_il",
								"annunci.stato",
								"annunci.external_videos",
								"annunci.media_updated_at",
								"annunci.homepage",
								"annunci.web",
								"annunci.disponibile_da",
								"annunci.persone",
								"annunci.permanenza",
								withImages(),
								withVideos(),
							])
							//ignora annunci che non sono disponibili
							//.where("annunci.web", "=", true)
							//.where("annunci.stato", "!=", "Sospeso")
							.orderBy("servizio_annunci.status_conferma", (ob) =>
								ob.asc().nullsLast(),
							)
							.orderBy("servizio_annunci.open_contatti_at", (ob) =>
								ob.asc().nullsLast(),
							)
							.orderBy("servizio_annunci.created_at", "asc")
							.whereRef(
								"servizio_annunci.servizio_id",
								"=",
								"servizio.servizio_id",
							),
					).as("annunci"),
					jsonArrayFrom(
						eb
							.selectFrom("ordini")
							.selectAll("ordini")
							.innerJoin(
								"prezziario",
								"prezziario.idprezziario",
								"ordini.packid",
							)
							.select("prezziario.testo_condizioni")
							.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
							.orderBy("ordini.created_at", "desc"),
					).as("ordini"),
				])
				.orderBy("servizio.created_at", "desc"),
			[
				"annunci.modificato_il",
				"annunci.created_at",
				"annunci.open_contatti_at",
				"annunci.contratto_decorrenza",
				"annunci.contratto_scadenza",
				"annunci.disponibile_da",
				"annunci.media_updated_at",
				"ordini.created_at",
				"ordini.paid_at",
			],
		).execute();

		return data;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error fetching servizio annunci: ${(e as Error).message}`,
		});
	}
};

export const getServizioDataById = async (
	servizioId: ServizioServizioId,
): Promise<ServizioData> => {
	try {
		const data = await withParsedDates(
			db
				.selectFrom("servizio")
				.leftJoin(
					"users_anagrafica",
					"servizio.user_id",
					"users_anagrafica.userid",
				)
				.selectAll("servizio")
				.select((eb) => [
					jsonObjectFrom(
						eb
							.selectFrom("users_storage")
							.select([
								"users_storage.storageId as storageId",
								"users_anagrafica.doc_personale_fronte_ref as ref",
							])
							.whereRef(
								"users_storage.user_storage_id",
								"=",
								"users_anagrafica.doc_personale_fronte_ref",
							),
					).as("doc_personale_fronte"),
					jsonObjectFrom(
						eb
							.selectFrom("users_storage")
							.select([
								"users_storage.storageId as storageId",
								"users_anagrafica.doc_personale_retro_ref as ref",
							])
							.whereRef(
								"users_storage.user_storage_id",
								"=",
								"users_anagrafica.doc_personale_retro_ref",
							),
					).as("doc_personale_retro"),
					jsonObjectFrom(
						eb
							.selectFrom("users_storage")
							.select([
								"users_storage.storageId as storageId",
								"servizio.doc_motivazione_ref as ref",
							])
							.whereRef(
								"users_storage.user_storage_id",
								"=",
								"servizio.doc_motivazione_ref",
							),
					).as("doc_motivazione"),
					jsonArrayFrom(
						eb
							.selectFrom("servizio_annunci")
							.selectAll("servizio_annunci")
							.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
							.select((_eb) => [
								"annunci.id",
								"annunci.codice",
								"annunci.comune",
								"annunci.provincia",
								"annunci.prezzo",
								"annunci.consegna",
								"annunci.numero_camere",
								"annunci.mq",
								"annunci.tipo",
								"annunci.titolo_it",
								"annunci.titolo_en",
								"annunci.desc_en",
								"annunci.desc_it",
								"annunci.modificato_il",
								"annunci.stato",
								"annunci.external_videos",
								"annunci.media_updated_at",
								"annunci.homepage",
								"annunci.web",
								"annunci.disponibile_da",
								"annunci.persone",
								"annunci.permanenza",
								withImages(),
								withVideos(),
							])
							//ignora annunci che non sono disponibili
							//.where("annunci.web", "=", true)
							//.where("annunci.stato", "!=", "Sospeso")
							.orderBy("servizio_annunci.status_conferma", (ob) =>
								ob.asc().nullsLast(),
							)
							.orderBy("servizio_annunci.open_contatti_at", (ob) =>
								ob.asc().nullsLast(),
							)
							.orderBy("servizio_annunci.created_at", "asc")
							.whereRef(
								"servizio_annunci.servizio_id",
								"=",
								"servizio.servizio_id",
							),
					).as("annunci"),
					jsonArrayFrom(
						eb
							.selectFrom("ordini")
							.selectAll("ordini")
							.innerJoin(
								"prezziario",
								"prezziario.idprezziario",
								"ordini.packid",
							)
							.select("prezziario.testo_condizioni")
							.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
							.orderBy("ordini.created_at", "desc"),
					).as("ordini"),
				])
				.orderBy("servizio.created_at", "desc")
				.where("servizio.servizio_id", "=", servizioId),
			[
				"annunci.created_at",
				"annunci.open_contatti_at",
				"annunci.contratto_decorrenza",
				"annunci.contratto_scadenza",
				"annunci.modificato_il",
				"annunci.disponibile_da",
				"annunci.media_updated_at",
				"ordini.created_at",
				"ordini.paid_at",
			],
		).executeTakeFirst();
		if (!data) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: `Servizio not found - servizioId: ${servizioId}`,
			});
		}

		return data;
	} catch (e) {
		throw new TRPCError({
			code: "NOT_FOUND",
			message: `Servizio annunci not found - servizioId: ${servizioId}: ${(e as Error).message}`,
		});
	}
};

export const addServizioAnnunci = async ({
	servizioId,
	annunci,
}: {
	servizioId: ServizioServizioId;
	annunci: AnnunciId[];
}) => {
	try {
		await db
			.insertInto("servizio_annunci")
			.values(
				annunci.map((annuncio, idx) => ({
					annunci_id: annuncio,
					created_at: add(new Date(), {
						//trick per assicurare che la data sia sempre diversa
						seconds: idx,
					}),
					servizio_id: servizioId,
				})),
			)
			.onConflict((oc) => oc.columns(["servizio_id", "annunci_id"]).doNothing())
			.execute();
		return true;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error adding servizio to annuncio: ${(e as Error).message}`,
		});
	}
};

export const getAnnunciAvailableToAdd = async (
	servizioId: ServizioServizioId,
) => {
	try {
		return await db.transaction().execute(async (tx) => {
			const servizioAnnunci = await tx
				.selectFrom("servizio_annunci")
				.select("annunci_id")
				.where("servizio_id", "=", servizioId)
				.execute();

			const ids = servizioAnnunci.map((item) => item.annunci_id);
			let qry = tx
				.selectFrom("annunci")
				.select(["id", "codice", "titolo_it"])
				.where("annunci.web", "=", true)
				.where("stato", "!=", "Sospeso");

			if (ids.length > 0) {
				qry = qry.where("id", "not in", ids);
			}
			return await qry.execute();
		});
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error fetching available annunci: ${(e as Error).message}`,
		});
	}
};

export const SbloccaContatti = async ({
	servizioId,
	annuncioId,
}: {
	servizioId: ServizioServizioId;
	annuncioId: AnnunciId;
}) => {
	try {
		const alreadyUnlocked = await db
			.selectFrom("servizio_annunci")
			.where("servizio_id", "=", servizioId)
			.where("open_contatti_at", "is not", null)
			.select((eb) => [eb.fn.count<number>("annunci_id").as("count")])
			.executeTakeFirst();
		if (alreadyUnlocked && alreadyUnlocked.count >= 10) {
			return false;
		}

		const data = await db.transaction().execute(async (tx) => {
			await tx
				.updateTable("servizio_annunci")
				.set({
					open_contatti_at: new Date(),
				})
				.where("servizio_id", "=", servizioId)
				.where("annunci_id", "=", annuncioId)
				.execute();

			const servizio = await tx
				.selectFrom("servizio")
				.select("user_id")
				.where("servizio_id", "=", servizioId)
				.executeTakeFirstOrThrow(() => new Error("Servizio not found"));

			const user = await tx
				.selectFrom("users")
				.select(["id", "username", "email", "telefono", "comms_enabled"])
				.where("id", "=", servizio.user_id)
				.executeTakeFirstOrThrow(() => new Error("User not found"));

			const anagrafica = await tx
				.selectFrom("users_anagrafica")
				.select("sesso")
				.where("userid", "=", servizio.user_id)
				.executeTakeFirstOrThrow(() => new Error("Anagrafica not found"));

			const annuncio = await tx
				.selectFrom("annunci")
				.where("id", "=", annuncioId)
				.select([
					"codice",
					"indirizzo",
					"email",
					"numero",
					"locatore",
					"tipo_locatore",
					"civico",
					"comune",
				])
				.executeTakeFirstOrThrow(() => new Error("Annuncio not found"));

			return {
				anagrafica,
				annuncio,
				annuncioId,
				servizioId,
				user,
			};
		});
		if (!data.user.comms_enabled) return true;

		const EmailProprietario: NewMailProps | null = data.annuncio.email
			? {
					template: {
						mailType: "emailContattoInteressato",
						props: {
							indirizzo: data.annuncio.indirizzo,
							nome_cognome: `${data.annuncio.locatore}${data.annuncio?.tipo_locatore ? ` (${data.annuncio.tipo_locatore})` : ""}`,
							nome_cognome_utente: data.user.username,
							sesso: data.anagrafica.sesso as "M" | "F" | null,
							telefono: data.user.telefono,
						},
					},
					mail: {
						subject: "Un interessato ha ricevuto i Suoi contatti",
						to: data.annuncio.email,
					},
				}
			: null;

		const numeroProprietario = data.annuncio.numero
			? data.annuncio.numero.replace(/\s+/g, "").split(",")[0] || null
			: null;

		const SmsProprietario: MinimalSMSData | null = numeroProprietario
			? {
					message: `${data.anagrafica.sesso ? (data.anagrafica.sesso === "M" ? "Il Sig." : "La Sig.ra") : "Il/La Sig./Sig.ra"} ${data.user.username}, con numero ${data.user.telefono} ha sbloccato i contatti per l'annuncio in ${data.annuncio.indirizzo} ${data.annuncio.civico} a ${data.annuncio.comune}.`,
					recipient: [numeroProprietario],
				}
			: null;

		if (canSendNotification()) {
			if (EmailProprietario) {
				await NewMail(EmailProprietario);
			}
			if (SmsProprietario) {
				await SendMessage(SmsProprietario);
			}
		} else {
			const lock_expires = nextTimeForNotification();
			if (EmailProprietario) {
				await addEventToQueue({
					data: {
						data: EmailProprietario,
						tipo: "email",
					},
					lock_expires,
				});
			}
			if (SmsProprietario) {
				await addEventToQueue({
					data: {
						data: SmsProprietario,
						tipo: "sms",
					},
					lock_expires,
				});
			}
		}
		return true;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error unlocking contatti: ${(e as Error).message}`,
		});
	}
};

export const interruzioneServizio = async (
	servizioId: ServizioServizioId,
	isAdmin: boolean,
) => {
	try {
		const servizio = await getServizioById(servizioId);

		if (!servizio) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: "Servizio not found",
			});
		}

		if (servizio.isInterrotto) {
			throw new TRPCError({
				code: "BAD_REQUEST",
				message: "Servizio already interrupted",
			});
		}
		if (servizio.decorrenza == null) {
			throw new TRPCError({
				code: "BAD_REQUEST",
				message: "Servizio decorrenza not set",
			});
		}
		const isWithinRipensamento = isBefore(
			new Date(),
			add(servizio.decorrenza, {
				days: servizio.interruzioneDays,
			}),
		);
		//se non è admin deve essere entro il periodo di ripensamento per poter interrompere il servizio
		if (!isWithinRipensamento && !isAdmin) {
			throw new TRPCError({
				code: "BAD_REQUEST",
				message: "Periodo di ripensamento scaduto",
			});
		}

		await db
			.updateTable("servizio")
			.set({
				isInterrotto: true,
			})
			.where("servizio_id", "=", servizioId)
			.execute();

		// NOTIFICHE EMAIL INTERRUZIONE SERVIZIO
		const utente = await getUser({ db, userId: servizio.user_id });

		if (utente.comms_enabled) {
			await NewMail({
				template: {
					mailType: "interruzione",
				},
				mail: {
					subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto.`,
					to: utente.email,
				},
				userId: utente.id,
			});
		}
		await NewMail({
			template: {
				mailType: "generic",
				props: {
					link: {
						href: `${env.BASE_URL}/area-riservata/admin/user-view/servizio/${servizio.user_id}/${servizioId}`,
						label: "Vai al servizio",
					},
					noreply: true,
					testo: `L'utente ha richiesto l'interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Puoi procedere con le prossime azioni.`,
					title: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`,
				},
			},
			mail: {
				subject: `Utente ${utente.username} ha richieto l'interruzione del servizio di ricerca affitto`,
				to: "web@infoalloggi.it",
			},
		});

		// cleanup coda notifiche: rimuove notifiche future relative a questo servizio
		await clearUserEmailQueue(utente.id);

		return true;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error interruzione servizio: ${(e as Error).message}`,
		});
	}
};

export const InteractionLogicTipologia = async ({
	tipologia,
	userId,
	annuncioId,
}: {
	tipologia: string;
	userId: UsersId;
	annuncioId: AnnunciId;
}) => {
	const parse = z.custom<TipologiaPosizioneEnum>();
	const parsedTipologia = parse.safeParse(tipologia);
	if (!parsedTipologia.success) {
		return { status: "invalid" as const };
	}
	const servizi = await db
		.selectFrom("servizio")
		.select(["tipologia", "servizio_id"])
		.where("tipologia", "=", parsedTipologia.data)
		.where("isOkAcconto", "=", true)
		.where("isInterrotto", "=", false)
		.where("decorrenza", "is not", null)
		.where("user_id", "=", userId)
		.select((eb) => [
			jsonObjectFrom(
				eb
					.selectFrom("servizio_annunci")
					.select("annunci_id")
					.where("servizio_annunci.annunci_id", "=", annuncioId)
					.whereRef("servizio.servizio_id", "=", "servizio_annunci.servizio_id")
					.limit(1),
			).as("alreadySaved"),
		])

		.executeTakeFirst();

	if (!servizi) {
		return { status: "not_found" as const };
	}

	if (servizi.alreadySaved) {
		return {
			servizioId: servizi.servizio_id,
			status: "already_saved" as const,
		};
	}
	return {
		servizioId: servizi.servizio_id,
		status: "found" as const,
		tipologia: servizi.tipologia,
	};
};

export const UtenteInteressatoAdAnnuncioEmail = async ({
	codice,
	nome,
	email,
	telefono,
	messaggio,
}: {
	codice: string;
	nome: string;
	email: string;
	telefono: string;
	messaggio: string;
}) => {
	try {
		await NewMail({
			template: {
				mailType: "contattoAnnuncioEmail",
				props: {
					codice,
					email,
					messaggio,
					nome,
					telefono,
				},
			},
			mail: {
				subject: "Contatto per annuncio",
				to: "web@infoalloggi.it",
			},
		});
		return {
			email,
			nome,
			telefono,
		};
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error sending email: ${(e as Error).message}`,
		});
	}
};

export const getCompatibileAnnunci = async ({
	servizioId,
	adminOverride = false,
}: {
	servizioId: ServizioServizioId;
	adminOverride?: boolean;
}): Promise<(AnnuncioRicerca & { alreadyRequested: boolean })[]> => {
	try {
		const servizio = await getServizioById(servizioId);
		if (!servizio) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: "Servizio not found",
			});
		}

		const interests = await GetUserInterests(servizio.user_id);

		let qry = db
			.selectFrom("annunci")
			.distinctOn("annunci.id")
			.select((eb) => [
				"id",
				"codice",
				"comune",
				"provincia",
				"mq",
				"prezzo",
				"desc_en",
				"desc_it",
				"titolo_en",
				"titolo_it",
				"tipo",
				"consegna",
				"stato",
				"web",
				"media_updated_at",
				"homepage",
				"numero_camere",
				"external_videos",
				"modificato_il",
				withImages(),
				withVideos(),
				eb
					.case()
					.when("id", "in", interests)
					.then(true)
					.else(false)
					.end()
					.as("alreadyRequested"),
			])
			.where("web", "=", true)
			.where("stato", "!=", "Sospeso")
			.where("tipo", "=", servizio.tipologia)
			.where("id", "not in", (eb) =>
				eb
					.selectFrom("servizio_annunci")
					.select("annunci_id")
					.where("servizio_id", "=", servizioId),
			);
		if (adminOverride) {
			return await qry.execute();
		}

		qry = qry.where("prezzo", "<=", servizio.budget * 1e2 * 1.2); // Allow 20% margin
		const { entromese } = servizio;
		const month = new Date().getMonth() + 1; // Months are 0-indexed in JS
		if (entromese !== null) {
			if (entromese === month) {
				qry = qry.where((eb) =>
					eb.or([
						eb("consegna", "=", 0),
						eb("consegna", "=", entromese),
						eb("consegna", "=", entromese + 1),
					]),
				);
			} else if (entromese === 0) {
				qry = qry.where((eb) =>
					eb.or([
						eb("consegna", "=", 0),
						eb("consegna", "=", month),
						eb("consegna", "=", month + 1),
					]),
				);
			} else {
				qry = qry.where((eb) =>
					eb.or([
						eb("consegna", "=", entromese),
						eb("consegna", "=", entromese + 1),
					]),
				);
			}
		}

		return await qry.execute();
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error fetching compatibile annunci: ${(e as Error).message}`,
		});
	}
};

export type RichiesteData = Pick<
	Servizio,
	| "servizio_id"
	| "user_id"
	| "isInterrotto"
	| "isOkAcconto"
	| "isOkSaldo"
	| "decorrenza"
	| "tipologia"
	| "onboardOk"
	| "isOkConsulenza"
	| "interruzioneDays"
> &
	Pick<
		Users,
		| "nome"
		| "username"
		| "note"
		| "telefono"
		| "extra_telefono_numero"
		| "extra_telefono_titolo"
	> & {
		annunci_servizio: ServizioAnnuncioData[];
		ordini_servizio: Pick<
			Ordini,
			| "isActive"
			| "ordine_id"
			| "created_at"
			| "packid"
			| "amount_cent"
			| "userid"
			| "type"
		>[];
	};

export const getRichieste = async (): Promise<RichiesteData[]> => {
	try {
		const data = await withParsedDates(
			db
				.selectFrom("servizio")
				.innerJoin("users", "users.id", "servizio.user_id")
				.select([
					"servizio.servizio_id",
					"servizio.user_id",
					"servizio.isInterrotto",
					"servizio.isOkAcconto",
					"servizio.isOkSaldo",
					"servizio.decorrenza",
					"servizio.tipologia",
					"servizio.onboardOk",
					"servizio.isOkConsulenza",
					"servizio.interruzioneDays",
				])
				.select([
					"users.nome",
					"users.username",
					"users.note",
					"users.telefono",
					"users.extra_telefono_numero",
					"users.extra_telefono_titolo",
				])
				.select((eb) => [
					jsonArrayFrom(
						eb
							.selectFrom("servizio_annunci")
							.selectAll("servizio_annunci")
							.innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
							.select((_eb) => [
								"annunci.id",
								"annunci.codice",
								"annunci.comune",
								"annunci.provincia",
								"annunci.prezzo",
								"annunci.consegna",
								"annunci.numero_camere",
								"annunci.mq",
								"annunci.tipo",
								"annunci.titolo_it",
								"annunci.titolo_en",
								"annunci.desc_en",
								"annunci.desc_it",
								"annunci.modificato_il",
								"annunci.stato",
								"annunci.external_videos",
								"annunci.media_updated_at",
								"annunci.homepage",
								"annunci.web",
								"annunci.disponibile_da",
								"annunci.persone",
								"annunci.permanenza",
								withImages(),
								withVideos(),
							])
							.orderBy("servizio_annunci.status_conferma", (ob) =>
								ob.asc().nullsLast(),
							)
							.orderBy("servizio_annunci.open_contatti_at", (ob) =>
								ob.asc().nullsLast(),
							)
							.orderBy("servizio_annunci.created_at", "asc")

							.whereRef(
								"servizio.servizio_id",
								"=",
								"servizio_annunci.servizio_id",
							)
							.orderBy("servizio_annunci.created_at", "desc"),
					).as("annunci_servizio"),
					jsonArrayFrom(
						eb
							.selectFrom("ordini")
							.select([
								"ordini.isActive",
								"ordini.ordine_id",
								"ordini.created_at",
								"ordini.packid",
								"ordini.amount_cent",
								"ordini.userid",
								"ordini.type",
							])
							.whereRef("ordini.servizio_id", "=", "servizio.servizio_id")
							.orderBy("ordini.created_at", "desc"),
					).as("ordini_servizio"),
				])
				.orderBy("servizio.created_at", "desc"),
			[
				"annunci_servizio.created_at",
				"annunci_servizio.open_contatti_at",
				"annunci_servizio.disponibile_da",
				"annunci_servizio.contratto_decorrenza",
				"annunci_servizio.contratto_scadenza",
				"annunci_servizio.modificato_il",
				"annunci_servizio.media_updated_at",
				"ordini_servizio.created_at",
			],
		).execute();

		return data;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error fetching richieste: ${(e as Error).message}`,
		});
	}
};

export const getIncrociAnnuncio = async (annuncioId: AnnunciId) => {
	try {
		const data = await db
			.selectFrom("servizio_annunci")
			.innerJoin(
				"servizio",
				"servizio.servizio_id",
				"servizio_annunci.servizio_id",
			)
			.innerJoin("users", "users.id", "servizio.user_id")
			.where("servizio_annunci.annunci_id", "=", annuncioId)
			.select([
				"servizio_annunci.open_contatti_at",
				"servizio_annunci.created_at",
				"servizio.servizio_id",
				"servizio.user_id",
				"users.username",
				"servizio_annunci.note",
			])
			.orderBy("servizio_annunci.created_at", "desc")
			.execute();

		return data;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Error fetching incroci annuncio: ${(e as Error).message}`,
		});
	}
};
