import { TRPCError } from "@trpc/server";
import { env } from "~/env";
import { filteredCaratteristiche } from "~/hooks/schedaAnnuncioUtils";
import { getStorageUrl } from "~/lib/storage_utils";
import type {
	Annunci,
	AnnunciId,
	AnnunciUpdate,
} from "~/schemas/public/Annunci";
import type { ImagesRefs } from "~/schemas/public/ImagesRefs";
import type { ServizioServizioId } from "~/schemas/public/Servizio";
import type { VideosRefs } from "~/schemas/public/VideosRefs";
import { db } from "~/server/db";
import type { AnnuncioTemplateData } from "~/server/services/typst.service";
import { withImages, withVideos } from "~/utils/kysely-helper";
import type { Result } from "~/utils/result";
import { revalidate } from "../utils/revalidationHelper";

// const ratelimit = new RateLimiterHandler({
//   windowSize: 10,
//   maxRequests: 10,
//   analytics: true,
// });
export type AnnunciWithMedia = Annunci & {
	tipo: string;
	images: Pick<ImagesRefs, "img" | "thumb">[];
	videos: Pick<VideosRefs, "video" | "thumb">[];
};

export const getAnnunciListHandler = async (): Promise<
	Pick<
		Annunci,
		"id" | "codice" | "titolo_it" | "comune" | "tipo" | "locatore" | "stato"
	>[]
> => {
	try {
		return await db
			.selectFrom("annunci")
			.select([
				"id",
				"codice",
				"titolo_it",
				"comune",
				"tipo",
				"locatore",
				"stato",
			])
			.execute();
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore getAnnunciListHandler: ${(e as Error).message}`,
		});
	}
};

export const getCodici_AnnunciHandler = async (): Promise<string[]> => {
	try {
		const codici = await db
			.selectFrom("annunci")
			.select("codice")
			.where("annunci.web", "=", true)
			.where("stato", "!=", "Sospeso")
			.execute();
		if (!codici) {
			return [];
		}

		return codici.map((c) => c.codice);
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore getCodici_AnnunciHandler: ${(e as Error).message}`,
		});
	}
};

export type AnnuncioData = Pick<
	Annunci,
	| "anno"
	| "accessori"
	| "caratteristiche"
	| "categorie"
	| "desc_en"
	| "desc_it"
	| "titolo_it"
	| "titolo_en"
	| "stato"
	| "id"
	| "codice"
	| "comune"
	| "consegna"
	| "external_videos"
	| "prezzo"
	| "numero_camere"
	| "numero_bagni"
	| "numero_balconi"
	| "numero_postiauto"
	| "numero_box"
	| "numero_vani"
	| "numero_terrazzi"
	| "mq"
	| "piano"
	| "piano_palazzo"
	| "lat_secondario"
	| "lon_secondario"
> & {
	tipo: string;
	media_updated_at: string | null;
	images: Pick<ImagesRefs, "img" | "thumb">[];
	videos: Pick<VideosRefs, "video" | "thumb">[];
	ogUrl: string;
	ogImage: string;
};

type getAnnuncioDataError =
	| { _tag: "NoAnnuncio" }
	| { _tag: "MalformedAnnuncio" }
	| { _tag: "InternalError"; cause: unknown };
export const getAnnuncioData = async ({
	cod,
}: {
	cod: string;
}): Promise<Result<AnnuncioData, getAnnuncioDataError>> => {
	try {
		const annuncio = await db
			.selectFrom("annunci")
			.select([
				"anno",
				"accessori",
				"caratteristiche",
				"categorie",
				"desc_en",
				"desc_it",
				"tipo",
				"titolo_it",
				"titolo_en",
				"stato",
				"media_updated_at",
				"id",
				"codice",
				"comune",
				"consegna",
				"external_videos",
				"prezzo",
				"numero_camere",
				"numero_bagni",
				"numero_balconi",
				"numero_postiauto",
				"numero_box",
				"numero_vani",
				"numero_terrazzi",
				"mq",
				"piano",
				"piano_palazzo",
				"lat_secondario",
				"lon_secondario",
			])
			.select((_eb) => [withImages(), withVideos()])
			.where("annunci.web", "=", true)
			.where("tipo", "is not", null)
			.where("codice", "=", cod)
			.executeTakeFirst();
		if (!annuncio) {
			return { ok: false, error: { _tag: "NoAnnuncio" } };
		}
		const { tipo, media_updated_at, ...rest } = annuncio;
		if (tipo === null) {
			return { ok: false, error: { _tag: "MalformedAnnuncio" } };
		}
		const ogImage =
			annuncio.images && annuncio.images.length > 0 && annuncio.images[0]
				? getStorageUrl({
						storageId: annuncio.images[0].img,
						params: {
							cacheKey: annuncio.media_updated_at?.toISOString(),
							media: "image",
						},
						host: env.BASE_URL,
					})
				: `${env.BASE_URL}/og.jpg`;
		return {
			ok: true,
			value: {
				...rest,
				media_updated_at: media_updated_at?.toISOString() || null,
				tipo,
				ogUrl: `${env.BASE_URL}/annuncio/${cod}`,
				ogImage,
			},
		};
	} catch (e) {
		return { ok: false, error: { _tag: "InternalError", cause: e } };
	}
};

export const getAnnunciById_rawImgUrls = async ({
	id,
}: {
	id: AnnunciId;
}): Promise<AnnunciWithMedia | null> => {
	try {
		const annuncio = await db
			.selectFrom("annunci")
			.selectAll()
			.select((_eb) => [withImages(), withVideos()])
			.where("id", "=", id)
			.where("tipo", "is not", null)
			.executeTakeFirst();
		if (!annuncio || annuncio.tipo === null) {
			throw new TRPCError({
				code: "NOT_FOUND",
				message: "Annuncio non trovato",
			});
		}

		return annuncio as AnnunciWithMedia;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore query getAnnuncioById : ${(e as Error).message}`,
		});
	}
};

export type AnnuncioRicercaWPosition = AnnuncioRicerca & {
	lat_secondario: string | null;
	lon_secondario: string | null;
};

export const get_AnnunciPositionsHandler = async ({
	tipologia,
	comune,
	consegna,
	minBudget,
	maxBudget,
}: {
	tipologia?: string;
	comune?: string;
	consegna?: number[];
	minBudget?: number;
	maxBudget?: number;
}): Promise<AnnuncioRicercaWPosition[]> => {
	try {
		let query = db
			.selectFrom("annunci")
			.select((_eb) => [
				"id",
				"codice",
				"comune",
				"provincia",
				"prezzo",
				"consegna",
				"numero_camere",
				"mq",
				"tipo",
				"titolo_it",
				"titolo_en",
				"desc_en",
				"desc_it",
				"web",
				"modificato_il",
				"stato",
				"external_videos",
				"lon_secondario",
				"lat_secondario",
				"media_updated_at",
				"homepage",
				withImages(),
				withVideos(),
			])

			.where("web", "=", true)
			.where("stato", "!=", "Sospeso")
			.orderBy("id", "asc");

		if (tipologia && tipologia !== "") {
			query = query.where("tipo", "=", tipologia);
		}
		if (comune && comune !== "") {
			query = query.where("comune", "like", comune);
		}
		if (consegna) {
			query = query.where("consegna", "in", consegna);
		}
		if (minBudget) {
			query = query.where("prezzo", ">=", minBudget);
		}
		if (maxBudget) {
			query = query.where("prezzo", "<=", maxBudget);
		}

		const annunci = await query.execute();
		if (!annunci) {
			return [];
		}

		return annunci;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore query getAnnunciPositions: ${(e as Error).message}`,
		});
	}
};

export type AnnuncioRicerca = 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"
> & {
	images: Pick<ImagesRefs, "img" | "thumb">[];
	videos: Pick<VideosRefs, "video" | "thumb">[];
};

export const getAnnunciRicerca = async ({
	tipologia,
	comune,
	consegna,
	sort,
	minBudget,
	maxBudget,
}: {
	tipologia?: string;
	comune?: string;
	consegna?: number[];
	sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
	minBudget?: number;
	maxBudget?: number;
}): Promise<AnnuncioRicerca[]> => {
	try {
		let query = db
			.selectFrom("annunci")
			.select((_eb) => [
				"id",
				"codice",
				"comune",
				"provincia",
				"prezzo",
				"consegna",
				"numero_camere",
				"mq",
				"tipo",
				"titolo_it",
				"titolo_en",
				"desc_en",
				"desc_it",
				"web",
				"modificato_il",
				"stato",
				"external_videos",
				"media_updated_at",
				"homepage",
				withImages(),
				withVideos(),
			])

			.where("web", "=", true)
			.where("stato", "!=", "Sospeso");

		if (tipologia && tipologia !== "") {
			query = query.where("tipo", "=", tipologia);
		}
		if (comune && comune !== "") {
			query = query.where("comune", "like", comune);
		}
		if (consegna) {
			query = query.where("consegna", "in", consegna);
		}
		if (minBudget) {
			query = query.where("prezzo", ">=", minBudget);
		}
		if (maxBudget) {
			query = query.where("prezzo", "<=", maxBudget);
		}

		switch (sort) {
			case "Recenti":
				query = query.orderBy("modificato_il", "desc");
				break;
			case "Prezzo":
				query = query.orderBy("prezzo", "asc");
				break;
			case "Consegna":
				query = query.orderBy("consegna", "asc");
				break;
			case "Mq":
				query = query.orderBy("mq", "asc");
				break;
			case undefined:
				break;
		}

		query = query.orderBy("id", "asc");

		return await query.execute();
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore query ricercaAnnunci: ${(e as Error).message}`,
		});
	}
};

export const getOptions_AnnunciHandler = async () => {
	try {
		const comuni = await db
			.selectFrom("annunci")
			.select(["comune", "tipo", "consegna", "prezzo"])
			.where("web", "=", true)
			.where("stato", "!=", "Sospeso")
			.distinct()
			.execute();
		const results: {
			comune: string;
			tipo: string;
			consegna: number;
			prezzo: number;
		}[] = [];

		for (const comune of comuni) {
			if (comune.comune && comune.tipo && comune.consegna && comune.prezzo) {
				results.push({
					comune: comune.comune,
					consegna: comune.consegna,
					tipo: comune.tipo,
					prezzo: comune.prezzo,
				});
			}
		}

		return results;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore query getComuni: ${(e as Error).message}`,
		});
	}
};

export const editAnnuncioHandler = async ({
	annuncioId,
	data,
}: {
	annuncioId: AnnunciId;
	data: AnnunciUpdate;
}) => {
	try {
		const annuncio = await db
			.updateTable("annunci")
			.set({
				...data,
				modificato_il: new Date(),
			})
			.where("id", "=", annuncioId)
			.returning("codice")
			.executeTakeFirstOrThrow(() => {
				throw new Error(`Annuncio not found - annuncioId: ${annuncioId}`);
			});
		await revalidate(`/annuncio/${annuncio.codice}`);
		return true;
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore interno: ${(e as Error).message}`,
		});
	}
};

export const getProprietarioDataHandler = async ({
	annuncioId,
	servizioId,
}: {
	annuncioId: AnnunciId;
	servizioId: ServizioServizioId;
}) => {
	try {
		const opened_at = await db
			.selectFrom("servizio_annunci")
			.select("open_contatti_at")
			.where("servizio_id", "=", servizioId)
			.where("annunci_id", "=", annuncioId)
			.executeTakeFirst();
		if (!opened_at?.open_contatti_at) {
			return { opened_at: null, propData: null };
		}

		const propData = await db
			.selectFrom("annunci")
			.select([
				"codice",
				"comune",
				"indirizzo",
				"civico",
				"provincia",
				"regione",
				"cap",
				"lat",
				"lon",
				"numero",
				"locatore",
				"tipo_locatore",
			])
			.where("id", "=", annuncioId)
			.executeTakeFirstOrThrow(() => {
				throw new Error(`Annuncio not found - annuncioId: ${annuncioId}`);
			});

		return { opened_at: opened_at.open_contatti_at, propData };
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore recupero dati proprietario: ${(e as Error).message}`,
		});
	}
};

export type ImgToRemove = {
	type: "img";
	id: ImagesRefs["img"];
};
export type VideoToRemove = {
	type: "video";
	id: VideosRefs["video"];
};
export const removeAnnuncioMedia = async ({
	id,
	type,
}: ImgToRemove | VideoToRemove) => {
	try {
		if (type === "img") {
			await db.deleteFrom("images_refs").where("img", "=", id).execute();
		}
		if (type === "video") {
			await db.deleteFrom("videos_refs").where("video", "=", id).execute();
		}
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore rimozione media: ${(e as Error).message}`,
		});
	}
};

export const getAnnunciSchedaData = async (
	id: AnnunciId,
): Promise<AnnuncioTemplateData> => {
	try {
		const data = await db
			.selectFrom("annunci")
			.select([
				"codice",
				"locatore",
				"numero",
				"indirizzo",
				"civico",
				"comune",
				"provincia",
				"cap",
				"lat",
				"lon",
				"tipo",
				"prezzo",
				"titolo_it",
				"desc_it",
				"disponibile_da",
				"tipo_locatore",
				"caratteristiche",
			])
			.where("id", "=", id)
			.executeTakeFirstOrThrow(
				() => new Error(`Annuncio non trovato - id: ${id}`),
			);

		if (
			!data.locatore ||
			!data.numero ||
			!data.indirizzo ||
			!data.lat ||
			!data.lon ||
			!data.tipo ||
			!data.prezzo ||
			!data.titolo_it ||
			!data.desc_it ||
			!data.disponibile_da
		) {
			throw new Error(
				`Dati incompleti per generazione scheda annuncio - id: ${id}`,
			);
		}
		return {
			codice: data.codice,
			numero: data.numero,
			nominativo: `${data.locatore}${data.tipo_locatore ? ` (${data.tipo_locatore})` : ""}`,
			indirizzo: `${data.indirizzo}, ${data.civico} ${data.comune} ${data.cap} (${data.provincia})`,
			lat: data.lat,
			lon: data.lon,
			tipo: data.tipo,
			prezzo: data.prezzo,
			titolo_it: data.titolo_it,
			desc_it: data.desc_it,
			disponibile_da: data.disponibile_da.toLocaleDateString("it-IT"),
			caratteristiche: data.caratteristiche
				? filteredCaratteristiche(data.caratteristiche)
						.filter((c) => c.value !== null)
						.map((c) => [c.text, c.value || ""])
				: [],
		};
	} catch (e) {
		throw new TRPCError({
			code: "INTERNAL_SERVER_ERROR",
			message: `Errore recupero dati scheda annuncio: ${(e as Error).message}`,
		});
	}
};
