import type {
  NewServizio,
  ServizioServizioId,
  ServizioUpdate,
} from "~/schemas/public/Servizio";
import { db } from "~/server/db";
import { TRPCError } from "@trpc/server";
import type { Users, UsersId } from "~/schemas/public/Users";
import type { UsersAnagraficaUpdate } from "~/schemas/public/UsersAnagrafica";
import { jsonArrayFrom, jsonObjectFrom } from "kysely/helpers/postgres";
import type { AnnunciId } from "~/schemas/public/Annunci";
import { add } from "date-fns";
import { z } from "zod";
import TipologiaPosizioneEnum from "~/schemas/public/TipologiaPosizioneEnum";
import { NewMail, type NewMailProps } from "~/server/services/mailer";
import type { ServizioAnnunciUpdate } from "~/schemas/public/ServizioAnnunci";
import type {
  Payments,
  PaymentsId,
  PaymentsUpdate,
} from "~/schemas/public/Payments";
import { sql } from "kysely";
import type {
  Prezziario,
  PrezziarioIdprezziario,
} from "~/schemas/public/Prezziario";
import OrderTypeEnum from "~/schemas/public/OrderTypeEnum";
import type { OrdiniOrdineId } from "~/schemas/public/Ordini";
import PaymentStatusEnum from "~/schemas/public/PaymentStatusEnum";
import type { PurchaseData } from "~/components/acquisto_receipt";
import type { Context } from "~/server/api/trpc";
import { getUser } from "~/server/services/user.service";
import { env } from "~/env.mjs";
import { cleanupUnusedOrders } from "~/server/controllers/ordini.controller";
import {
  addEventToQueue,
  canSendNotification,
  nextTimeForNotification,
} from "~/server/controllers/event_queue.controller";
import {
  SendMessage,
  type MinimalSMSData,
} from "~/server/controllers/skebby.controller";

export const addServizio = async (data: NewServizio) => {
  try {
    return await db
      .insertInto("servizio")
      .values(data)
      .returningAll()
      .executeTakeFirstOrThrow();
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error adding new servizio: " + (e as Error).message,
    });
  }
};

export const getServizioById = async (servizioId: ServizioServizioId) => {
  try {
    return await db
      .selectFrom("servizio")
      .selectAll()
      .where("servizio_id", "=", servizioId)
      .executeTakeFirstOrThrow();
  } catch (e) {
    throw new TRPCError({
      code: "NOT_FOUND",
      message: "Servizio not found: " + (e as Error).message,
    });
  }
};

export const updateServizio = async ({
  servizioId,
  data,
}: {
  servizioId: ServizioServizioId;
  data: ServizioUpdate;
}) => {
  try {
    return await db
      .updateTable("servizio")
      .set(data)
      .where("servizio_id", "=", servizioId)
      .returningAll()
      .executeTakeFirstOrThrow();
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error updating servizio: " + (e as Error).message,
    });
  }
};

export const deleteServizio = async (servizioId: ServizioServizioId) => {
  try {
    await db
      .deleteFrom("servizio")
      .where("servizio_id", "=", servizioId)
      .executeTakeFirstOrThrow();
    return true;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error deleting servizio: " + (e as Error).message,
    });
  }
};

export const getServiziByUserId = async (userId: UsersId) => {
  try {
    return await db
      .selectFrom("servizio")
      .selectAll()
      .where("user_id", "=", userId)
      .orderBy("created_at", "asc")
      .execute();
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error fetching servizi by user ID: " + (e as Error).message,
    });
  }
};
export const getPreOnboardData = async (servizioId: ServizioServizioId) => {
  try {
    const servizio = await getServizioById(servizioId);

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

    const userData = await db
      .selectFrom("users")
      .select([
        "users.id",
        "users.nome",
        "users.cognome",
        "users.email",
        "users.telefono",
      ])
      .where("id", "=", servizio.user_id)
      .executeTakeFirstOrThrow();

    if (!userData) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "User not found",
      });
    }
    const hasPrevious = await db
      .selectFrom("servizio")
      .select("servizio_id")
      .where("user_id", "=", servizio.user_id)
      .where("servizio_id", "!=", servizioId)
      .where("isOkAcconto", "=", true)
      .executeTakeFirst();

    return { servizio, userData, hasPrevious: !!hasPrevious };
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error fetching pre-onboard data: " + (e as Error).message,
    });
  }
};
export const getDataPerAcquisto = async (servizioId: ServizioServizioId) => {
  try {
    const servizio = await getServizioById(servizioId);

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

    const userData = await db
      .selectFrom("users")
      .select([
        "users.id",
        "users.nome",
        "users.cognome",
        "users.email",
        "users.telefono",
      ])
      .where("id", "=", servizio.user_id)
      .executeTakeFirstOrThrow();

    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 {
      servizio,
      userData,
      anagrafica,
    };
  } catch (e) {
    throw new TRPCError({
      code: "NOT_FOUND",
      message: "Servizio not found: " + (e as Error).message,
    });
  }
};

export const postAcquistoDataHandler = async ({
  servizioId,
  userId,
  user,
  anagrafica,
  servizio,
  skipOrdine = false,
}: {
  servizioId: ServizioServizioId;
  userId: UsersId;
  user: Pick<Users, "nome" | "cognome" | "email" | "telefono">;
  anagrafica: UsersAnagraficaUpdate;
  servizio: ServizioUpdate;
  skipOrdine?: boolean;
}) => {
  try {
    const servizioData = await db.transaction().execute(async (tx) => {
      await tx
        .updateTable("users")
        .set(user)
        .where("id", "=", userId)
        .executeTakeFirstOrThrow();

      const { idanagrafica, ...rest } = anagrafica;
      if (idanagrafica) {
        await tx
          .updateTable("users_anagrafica")
          .set({
            ...rest,
            userid: userId,
          })
          .where("idanagrafica", "=", idanagrafica)
          .returningAll()
          .executeTakeFirstOrThrow();
      } else {
        await tx
          .insertInto("users_anagrafica")
          .values({
            ...rest,
            userid: userId,
          })
          .returningAll()
          .executeTakeFirstOrThrow();
      }

      await tx
        .updateTable("users_anagrafica")
        .set(anagrafica)
        .where("userid", "=", userId)
        .executeTakeFirstOrThrow();

      return await tx
        .updateTable("servizio")
        .set(servizio)
        .where("servizio_id", "=", servizioId)
        .returningAll()
        .executeTakeFirstOrThrow();
    });

    if (skipOrdine) {
      await updateServizio({
        servizioId,
        data: {
          isOkAcconto: true,
          decorrenza: new Date(),
        },
      });
      return null;
    }
    //cleanup unused payments

    await db
      .deleteFrom("payments")
      .where("servizio_id", "=", servizioId)
      .where("paymentstatus", "is", null)
      .execute();

    if (
      !servizioData ||
      servizioData.isInterrotto ||
      servizioData.isOkAcconto ||
      servizioData.decorrenza !== null
    ) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Cannot create payment for this servizioData",
      });
    }
    const packId =
      servizioData.tipologia == TipologiaPosizioneEnum.Transitorio
        ? ("ACCONTO_BD" as PrezziarioIdprezziario)
        : ("ACCONTO_CA" as PrezziarioIdprezziario);

    const acconto = await db
      .selectFrom("prezziario")
      .select("idprezziario")
      .where("isActive", "=", true)
      .where("idprezziario", "=", packId)
      .executeTakeFirst();

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

    const existingOrder = await db
      .selectFrom("ordini")
      .select("ordini.ordine_id")
      .where("ordini.servizio_id", "=", servizioData.servizio_id)
      .where("ordini.packid", "=", acconto.idprezziario)
      .where("ordini.type", "=", OrderTypeEnum.Acconto)
      .where("ordini.isActive", "=", false)
      .orderBy("ordini.created_at", "desc")
      .executeTakeFirst();

    if (existingOrder) {
      await db
        .deleteFrom("ordini")
        .where("ordini.isActive", "=", false)
        .where("ordine_id", "!=", existingOrder.ordine_id)
        .execute();
      return existingOrder.ordine_id;
    }

    const order = await db
      .insertInto("ordini")
      .values({
        servizio_id: servizioData.servizio_id,
        packid: acconto.idprezziario,
        type: OrderTypeEnum.Acconto,
        userid: userId,
      })
      .returning("ordine_id")
      .executeTakeFirstOrThrow();

    return order.ordine_id;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error updating data for acquisto: " + (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,
};

export const setupSaldoServizio = async ({
  servizioId,
  annuncioId,
}: {
  servizioId: ServizioServizioId;
  annuncioId: AnnunciId;
}) => {
  try {
    return await db.transaction().execute(async (tx) => {
      // Cleanup previous payments and orders
      await cleanupUnusedOrders({ db: tx });

      const servizio = await tx
        .selectFrom("servizio")
        .selectAll()
        .where("servizio_id", "=", servizioId)
        .executeTakeFirstOrThrow();

      if (
        !servizio.isOkAcconto ||
        servizio.isInterrotto ||
        servizio.decorrenza == null ||
        servizio.isOkSaldo
      ) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Servizio not found",
        });
      }

      const annuncio = await tx
        .selectFrom("servizio_annunci")
        .selectAll()
        .where("servizio_id", "=", servizioId)
        .where("annunci_id", "=", annuncioId)
        .executeTakeFirstOrThrow();
      if (
        annuncio.user_confirmed_at == null ||
        annuncio.accettato_conferma_at == null
      ) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Annuncio not found",
        });
      }
      let saldo:
        | Pick<
            Prezziario,
            "idprezziario" | "prezzo_cent" | "nome_it" | "nome_en" | "sconto"
          >
        | undefined;

      const qry = tx
        .selectFrom("prezziario")
        .select(["idprezziario", "prezzo_cent", "nome_it", "nome_en", "sconto"])
        .where("isActive", "=", true);

      if (servizio.tipologia == TipologiaPosizioneEnum.Transitorio) {
        const mpp =
          servizio.permanenza && SaldoTransitorioMapping[servizio.permanenza];
        if (!servizio.permanenza || !mpp) {
          throw new TRPCError({
            code: "NOT_FOUND",
            message: "Permanenza not set for transitorio servizio",
          });
        }
        saldo = await qry.where("idprezziario", "=", mpp).executeTakeFirst();
      } else if (servizio.tipologia == TipologiaPosizioneEnum.Stabile) {
        if (servizio.budget >= 500) {
          saldo = await qry
            .where("idprezziario", "=", "SALDOCA500+" as PrezziarioIdprezziario)

            .executeTakeFirst();
        } else {
          saldo = await qry
            .where("idprezziario", "=", "SALDOCA500" as PrezziarioIdprezziario)
            .executeTakeFirst();
        }
      }

      if (!saldo) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Acconto not found",
        });
      }
      await tx
        .deleteFrom("payments")
        .where("servizio_id", "=", servizioId)
        .where("paymentstatus", "is", null)
        .execute();

      const order = await db
        .insertInto("ordini")
        .values({
          servizio_id: servizio.servizio_id,
          packid: saldo.idprezziario,
          type: OrderTypeEnum.Saldo,
          userid: servizio.user_id,
        })
        .returning("ordine_id")
        .executeTakeFirstOrThrow();

      return order.ordine_id;
    });
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error setting up saldo servizio: " + (e as Error).message,
    });
  }
};

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

type TipoEnum = keyof typeof ConsulenzaPriceMapping;
export const setupSaldoConsulenzaServizio = async ({
  servizioId,
  annuncioId,
}: {
  servizioId: ServizioServizioId;
  annuncioId: AnnunciId;
}) => {
  try {
    return await db.transaction().execute(async (tx) => {
      // Cleanup previous payments and orders
      await cleanupUnusedOrders({ db: tx });

      const servizio = await tx
        .selectFrom("servizio")
        .selectAll()
        .where("servizio_id", "=", servizioId)
        .executeTakeFirstOrThrow();

      if (
        !servizio.isOkAcconto ||
        servizio.isInterrotto ||
        servizio.decorrenza == null ||
        !servizio.isOkSaldo ||
        servizio.isOkConsulenza
      ) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Non serve saldo per questo servizio",
        });
      }

      const annuncio = await tx
        .selectFrom("servizio_annunci")
        .selectAll()
        .where("servizio_id", "=", servizioId)
        .where("annunci_id", "=", annuncioId)
        .executeTakeFirstOrThrow();
      if (
        annuncio.user_confirmed_at == null ||
        annuncio.accettato_conferma_at == null ||
        !annuncio.contratto_tipo
      ) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Annuncio not found or contratto not set",
        });
      }

      if (
        !Object.keys(ConsulenzaPriceMapping).includes(annuncio.contratto_tipo)
      ) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Consulenza type not found",
        });
      }

      const pack = await tx
        .selectFrom("prezziario")
        .select(["idprezziario", "prezzo_cent", "nome_it", "nome_en", "sconto"])
        .where("isActive", "=", true)
        .where(
          "idprezziario",
          "=",
          ConsulenzaPriceMapping[annuncio.contratto_tipo as TipoEnum],
        )
        .executeTakeFirst();

      if (!pack) {
        throw new TRPCError({
          code: "NOT_FOUND",
          message: "Pack not found",
        });
      }
      await tx
        .deleteFrom("payments")
        .where("servizio_id", "=", servizioId)
        .where("paymentstatus", "is", null)
        .execute();

      const order = await db
        .insertInto("ordini")
        .values({
          servizio_id: servizio.servizio_id,
          packid: pack.idprezziario,
          type: OrderTypeEnum.Consulenza,
          userid: servizio.user_id,
        })
        .returning("ordine_id")
        .executeTakeFirstOrThrow();

      return order.ordine_id;
    });
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error setting up consulenza servizio: " + (e as Error).message,
    });
  }
};
export type ServizioData = Awaited<
  ReturnType<typeof getAllServizioAnnunci>
>[number];
export const getAllServizioAnnunci = async (userId: UsersId) => {
  try {
    return await db
      .selectFrom("servizio")
      .where("user_id", "=", userId)
      .select((eb) => [
        "servizio.servizio_id",
        "servizio.created_at",
        "servizio.decorrenza",
        "servizio.tipologia",
        "servizio.isInterrotto",
        "servizio.isOkAcconto",
        "servizio.isOkSaldo",
        "servizio.isOkConsulenza",
        jsonArrayFrom(
          eb
            .selectFrom("servizio_annunci")
            .selectAll("servizio_annunci")
            .innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
            .select([
              "annunci.id",
              "annunci.codice",
              "annunci.prezzo",
              "annunci.desc_en",
              "annunci.desc_it",
              "annunci.url_immagini",
              "annunci.titolo_en",
              "annunci.titolo_it",
              "annunci.tipo",
              "annunci.consegna",
              "annunci.stato",
              "annunci.web",
            ])
            //ignora annunci che non sono disponibili
            .where("annunci.web", "=", true)
            .where("annunci.stato", "!=", "Sospeso")
            .orderBy("servizio_annunci.user_confirmed_at", (ob) =>
              ob.asc().nullsLast(),
            )
            .orderBy("servizio_annunci.open_contatti_at", (ob) =>
              ob.asc().nullsFirst(),
            )

            .orderBy("servizio_annunci.created_at", "asc")
            .whereRef(
              "servizio_annunci.servizio_id",
              "=",
              "servizio.servizio_id",
            ),
        ).as("annunci"),
      ])

      .orderBy("servizio.created_at", "desc")
      .execute();
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error fetching servizio annunci: " + (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) => ({
          servizio_id: servizioId,
          annunci_id: annuncio,
          created_at: add(new Date(), {
            //trick per assicurare che la data sia sempre diversa
            seconds: idx,
          }),
        })),
      )
      .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 getServizioAnnuncio = async ({
  servizioId,
  annuncioId,
}: {
  servizioId: ServizioServizioId;
  annuncioId: AnnunciId;
}) => {
  try {
    return await db
      .selectFrom("servizio_annunci")
      .selectAll()
      .where("servizio_id", "=", servizioId)
      .where("annunci_id", "=", annuncioId)
      .executeTakeFirstOrThrow();
  } catch (e) {
    throw new TRPCError({
      code: "NOT_FOUND",
      message: "Servizio not found: " + (e as Error).message,
    });
  }
};

export const updateServizioAnnuncio = async ({
  servizioId,
  annuncioId,
  data,
}: {
  servizioId: ServizioServizioId;
  annuncioId: AnnunciId;
  data: ServizioAnnunciUpdate;
}) => {
  try {
    await db
      .updateTable("servizio_annunci")
      .set(data)
      .where("servizio_id", "=", servizioId)
      .where("annunci_id", "=", annuncioId)
      .execute();

    const servizio = await getServizioById(servizioId);
    const utente = await getUser({ db, userId: servizio.user_id });
    //ADMIN CONFERMA LAVORATA EMAIL
    if (data.hasConfermaAdmin) {
      await NewMail({
        userId: utente.id,
        mailType: "generic",
        to: utente.email,
        subject: "Conferma lavorata per il servizio di ricerca affitto",
        props: {
          noreply: true,
          title: "Conferma lavorata per il servizio di ricerca affitto",
          testo: `La conferma è stata lavorata con successo. Puoi procedere con le prossime azioni.`,
          link: {
            href: `${env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`,
            label: "Vai al servizio",
          },
        },
      });
    }

    // USER ACCETTA CONFERMA EMAIL
    if (data.accettato_conferma_at) {
      await NewMail({
        mailType: "generic",
        to: "web@infoalloggi.it",
        subject: `Utente ${utente.username} ha accettato le condizioni di conferma`,
        props: {
          noreply: true,
          title: `Utente ${utente.username} ha accettato le condizioni di conferma`,
          testo: `L'utente ha accettato le condizioni di conferma per la locazione. Puoi procedere con le prossime azioni.`,
          link: {
            href: `${env.NEXT_PUBLIC_BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`,
            label: "Vai al servizio",
          },
        },
      });
    }
    return true;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error updating servizio from 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"])
        .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 deleteServizioAnnuncio = async (
  servizioId: ServizioServizioId,
  annuncioId: AnnunciId,
) => {
  try {
    await db
      .deleteFrom("servizio_annunci")
      .where("servizio_id", "=", servizioId)
      .where("annunci_id", "=", annuncioId)
      .execute();
    return true;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error deleting servizio from annuncio: " + (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"])
        .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([
          "indirizzo",
          "email",
          "numero",
          "locatore",
          "civico",
          "comune",
        ])
        .executeTakeFirstOrThrow(() => new Error("Annuncio not found"));

      return {
        user,
        anagrafica,
        annuncio,
        servizioId,
        annuncioId,
      };
    });

    const EmailProprietario: NewMailProps | null = data.annuncio.email
      ? {
          mailType: "emailContattoInteressato",
          to: data.annuncio.email,
          subject: "Un interessato ha ricevuto i Suoi contatti",
          props: {
            indirizzo: data.annuncio.indirizzo,
            nome_cognome: data.annuncio.locatore || "",
            nome_cognome_utente: data.user.username,
            telefono: data.user.telefono,
            sesso: data.anagrafica.sesso,
          },
        }
      : null;

    const EmailInteressato: NewMailProps = {
      mailType: "generic",
      to: data.user.email,
      subject: "Contatti sbloccati per il servizio di ricerca affitto",
      props: {
        noreply: true,
        title: "Contatti sbloccati per il servizio di ricerca affitto",
        testo: `I contatti per l'annuncio ${annuncioId} sono stati sbloccati con successo.`,
        link: {
          href: `${env.NEXT_PUBLIC_BASE_URL}/area-riservata/dashboard#${servizioId}-${annuncioId}`,
          label: "Vai al servizio",
        },
      },
    };

    const numeroProprietario =
      data.annuncio.numero &&
      data.annuncio.numero.startsWith("+39") &&
      data.annuncio.numero[3] == "3"
        ? data.annuncio.numero.split(",")[0]
        : null;

    const SmsProprietario: MinimalSMSData | null = numeroProprietario
      ? {
          recipient: [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}.`,
        }
      : null;

    await NewMail(EmailInteressato);

    if (canSendNotification()) {
      if (EmailProprietario) {
        await NewMail(EmailProprietario);
      }
      if (SmsProprietario) {
        await SendMessage(SmsProprietario);
      }
    } else {
      const lock_expires = nextTimeForNotification();
      if (EmailProprietario) {
        await addEventToQueue({
          lock_expires,
          data: {
            tipo: "email",
            data: EmailProprietario,
          },
        });
      }
      if (SmsProprietario) {
        await addEventToQueue({
          lock_expires,
          data: {
            tipo: "sms",
            data: SmsProprietario,
          },
        });
      }
    }
    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) => {
  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 =
      new Date() <=
      add(servizio.decorrenza, {
        days: 14,
      });

    if (!isWithinRipensamento) {
      throw new TRPCError({
        code: "BAD_REQUEST",
        message: "Periodo di ripensamento scaduto",
      });
    }

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

    // NOTIFICHE EMAIL INTERRUZIONE SERVIZIO
    const utente = await getUser({ db, userId: servizio.user_id });
    await NewMail({
      userId: utente.id,
      mailType: "generic",
      to: utente.email,
      subject: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`,
      props: {
        noreply: true,
        title: `Servizio Ricerca Affitto ${servizio.tipologia} interrotto`,
        testo: `Abbiamo ricevuto la tua richiesta di interruzione del servizio di ricerca affitto per la tipologia ${servizio.tipologia}. Il servizio è stato interrotto con successo.`,
      },
    });

    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 {
      status: "already_saved" as const,
      servizioId: servizi.servizio_id,
    };
  }
  return {
    status: "found" as const,
    servizioId: servizi.servizio_id,
    tipologia: servizi.tipologia,
  };
};

export const sendServizioEmail = async ({
  servizioId,
  userId,
}: {
  servizioId: ServizioServizioId;
  userId: UsersId;
}) => {
  try {
    const user = await db
      .selectFrom("users")
      .select(["email", "nome"])
      .where("id", "=", userId)
      .executeTakeFirstOrThrow();
    if (!user) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "User not found",
      });
    }
    const annunci = await db
      .selectFrom("servizio_annunci")
      .innerJoin("annunci", "annunci.id", "servizio_annunci.annunci_id")
      .select([
        "annunci.titolo_it",
        "annunci.url_immagini",
        "annunci.codice",
        "annunci.prezzo",
      ])
      .where("servizio_annunci.servizio_id", "=", servizioId)
      .execute();

    const { email, nome } = user;
    await NewMail({
      userId,
      mailType: "onboardingServizio",
      to: email,
      props: {
        nome,
        token: servizioId,
        annunci: annunci.map((annuncio) => ({
          titolo: annuncio.titolo_it,
          immagine: annuncio.url_immagini
            ? annuncio.url_immagini[0] || null
            : null,
          codice: annuncio.codice,
          prezzo: annuncio.prezzo,
        })),
      },
      subject: "Procedi ora su Infoalloggi.it",
    });
    await db
      .updateTable("servizio")
      .set({
        isSent: true,
      })
      .where("servizio_id", "=", servizioId)
      .executeTakeFirstOrThrow();
    return true;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error sending email: " + (e as Error).message,
    });
  }
};

export const userConfirmImmobile = async ({
  servizioId,
  annuncioId,
}: {
  servizioId: ServizioServizioId;
  annuncioId: AnnunciId;
}) => {
  try {
    const servizio = await getServizioById(servizioId);
    if (
      !servizio ||
      servizio.isInterrotto ||
      !servizio.isOkAcconto ||
      servizio.decorrenza == null
    ) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Servizio not found",
      });
    }
    await db
      .updateTable("servizio_annunci")
      .set({
        user_confirmed_at: new Date(),
      })
      .where("servizio_id", "=", servizioId)
      .where("annunci_id", "=", annuncioId)
      .executeTakeFirstOrThrow();

    // NOTIFICHE EMAIL CONFERMA IMMOBILE

    const utente = await getUser({ db, userId: servizio.user_id });

    const annuncio_codice = await db
      .selectFrom("annunci")
      .select("codice")
      .where("id", "=", annuncioId)
      .executeTakeFirstOrThrow();

    await NewMail({
      mailType: "generic",
      to: "web@infoalloggi.it",
      subject: `Nuova conferma immobile ${annuncio_codice.codice}`,
      props: {
        noreply: true,
        title: `Nuova conferma immobile ${annuncio_codice.codice}`,
        testo: `L'utente ${utente.username} ha confermato l'immobile con codice ${annuncio_codice.codice}.`,
        link: {
          href: `${env.NEXT_PUBLIC_BASE_URL}/area-riservata/admin/user-view/ricerca/${servizio.user_id}#${servizioId}-${annuncioId}`,
          label: "Visualizza Annuncio",
        },
      },
    });

    await NewMail({
      userId: utente.id,
      mailType: "generic",
      to: utente.email,
      subject: `Conferma immobile ${annuncio_codice.codice} ricevuta`,
      props: {
        noreply: true,
        title: `Conferma immobile ${annuncio_codice.codice} ricevuta`,
        testo: `La tua conferma per l'immobile con codice ${annuncio_codice.codice} è stata ricevuta. Il nostro team la esaminerà a breve. Riceverai una notifica via email per procedere.`,
      },
    });
    return true;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error confirming immobile: " + (e as Error).message,
    });
  }
};
export const isOrdineAwaitingPayment = async (ordineId: OrdiniOrdineId) => {
  try {
    const ordine = await db
      .selectFrom("ordini")
      .select("ordine_id")
      .where("ordine_id", "=", ordineId)
      .where("isActive", "=", false)
      .executeTakeFirst();

    if (!ordine) {
      console.error("Ordine not found:", ordineId);
      return false;
    }

    const payment = await db
      .selectFrom("payments")
      .select("id")
      .where("ordine_id", "=", ordineId)
      .where("paymentstatus", "=", PaymentStatusEnum.success)
      .executeTakeFirst();

    if (!payment) {
      return true;
    }
    console.error("Payment already exists for ordine:", ordineId);
    return false;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error checking ordine payment status: " + (e as Error).message,
    });
  }
};
export const getServizioPagamentoData = async (ordineId: OrdiniOrdineId) => {
  try {
    await db
      .deleteFrom("payments")
      .where("ordine_id", "=", ordineId)
      .where("paymentstatus", "is", null)
      .execute();

    const ordine = await db
      .selectFrom("ordini")
      .selectAll()
      .where("ordine_id", "=", ordineId)
      .executeTakeFirstOrThrow();

    const pack = await db
      .selectFrom("prezziario")
      .selectAll()
      .where("idprezziario", "=", ordine.packid)
      .executeTakeFirstOrThrow();

    const payment = await db
      .insertInto("payments")
      .values({
        ordine_id: ordineId,
        servizio_id: ordine.servizio_id,
        amount_cent: pack.prezzo_cent,
        paymentname: pack.nome_it,
        userid: ordine.userid,
      })
      .returningAll()
      .executeTakeFirstOrThrow();

    const servizio = await db
      .selectFrom("servizio")
      .selectAll()
      .where("servizio.servizio_id", "=", payment.servizio_id)
      .executeTakeFirstOrThrow();

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

    return {
      servizio,
      ordine,
      payment,
    };
  } catch (e) {
    throw new TRPCError({
      code: "NOT_FOUND",
      message: "Servizio not found: " + (e as Error).message,
    });
  }
};

type Pagamento = Payments & Pick<Users, "username">;
export const getAllPagamenti = async (): Promise<Pagamento[]> => {
  try {
    return await db
      .selectFrom("payments")
      .selectAll()
      .innerJoin("users", "users.id", "payments.userid")
      .select("users.username")
      .orderBy("payments.created_at", "desc")
      .execute();
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error fetching user pagamenti: " + (e as Error).message,
    });
  }
};

export const updatePagamento = async ({
  paymentId,
  data,
}: {
  paymentId: PaymentsId;
  data: PaymentsUpdate;
}) => {
  try {
    return await db
      .updateTable("payments")
      .set(data)
      .where("id", "=", paymentId)
      .returningAll()
      .executeTakeFirstOrThrow();
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error updating pagamento: " + (e as Error).message,
    });
  }
};

export const removePagamento = async (paymentId: PaymentsId) => {
  try {
    await db.deleteFrom("payments").where("id", "=", paymentId).execute();

    return true;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error removing pagamento: " + (e as Error).message,
    });
  }
};
export const getUserPagamenti = async (
  userId: UsersId,
): Promise<Pagamento[]> => {
  try {
    return await db
      .selectFrom("payments")
      .selectAll()
      .select([sql.lit("").as("username")])
      .where("payments.userid", "=", userId)
      .orderBy("payments.created_at", "desc")
      .execute();
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error fetching user pagamenti: " + (e as Error).message,
    });
  }
};

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

export const getCompatibileAnnunci = async (servizioId: ServizioServizioId) => {
  try {
    const servizio = await getServizioById(servizioId);
    if (!servizio) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Servizio not found",
      });
    }

    let qry = db
      .selectFrom("annunci")
      .select([
        "annunci.id",
        "annunci.codice",
        "annunci.prezzo",
        "annunci.desc_en",
        "annunci.desc_it",
        "annunci.url_immagini",
        "annunci.titolo_en",
        "annunci.titolo_it",
        "annunci.tipo",
        "annunci.consegna",
        "annunci.stato",
        "annunci.web",
      ])
      .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),
      )
      .where("prezzo", "<=", servizio.budget * 1e2 * 1.2); // Allow 20% margin

    const month = new Date().getMonth() + 1; // Months are 0-indexed in JS
    if (servizio.entromese == month) {
      qry = qry.where((eb) =>
        eb.or([
          eb("consegna", "=", 0),
          eb("consegna", "=", servizio.entromese),
        ]),
      );
    } else if (servizio.entromese == 0) {
      qry = qry.where((eb) =>
        eb.or([eb("consegna", "=", 0), eb("consegna", "=", month)]),
      );
    } else {
      qry = qry.where("consegna", "=", servizio.entromese);
    }

    const annunci = await qry.execute();

    return annunci;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error fetching compatibile annunci: " + (e as Error).message,
    });
  }
};

export const getOrdineRicevutaData = async ({
  ordineId,
  ctx,
}: {
  ordineId: OrdiniOrdineId;
  ctx: Context;
}) => {
  try {
    const data = await db
      .selectFrom("payments")
      .select([
        "payments.id",
        "payments.userid",
        "payments.amount_cent",
        "payments.paymentname",
        "payments.created_at",
        "payments.paymentmethod",
      ])
      .select((eb) => [
        jsonObjectFrom(
          eb
            .selectFrom("users")
            .whereRef("users.id", "=", "payments.userid")
            .select(["users.nome", "users.cognome"])

            .limit(1),
        ).as("user"),
        jsonObjectFrom(
          eb
            .selectFrom("users_anagrafica")
            .whereRef("users_anagrafica.userid", "=", "payments.userid")
            .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",
            ])
            .limit(1),
        ).as("anagrafica"),
      ])
      .where("payments.ordine_id", "=", ordineId)
      .where("payments.paymentstatus", "=", PaymentStatusEnum.success)

      .executeTakeFirstOrThrow();

    if (!ctx.session) {
      throw new TRPCError({
        code: "UNAUTHORIZED",
        message: "User not authenticated",
      });
    }
    if (!ctx.session.isAdmin && data.userid !== ctx.session.id) {
      throw new TRPCError({
        code: "FORBIDDEN",
        message: "You do not have permission to access this ordine",
      });
    }

    if (!data || !data.user || !data.anagrafica) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Ordine not found",
      });
    }
    return {
      paymentId: data.id,
      date: data.created_at,
      clienteNome: `${data.user.nome} ${data.user.cognome}`,
      clienteIndirizzo: `${data.anagrafica.via_residenza} ${data.anagrafica.civico_residenza}\n${data.anagrafica.comune_residenza} (${data.anagrafica.provincia_residenza}) ${data.anagrafica.cap_residenza}\n${data.anagrafica.nazione_residenza}`,
      paymentMethod: data.paymentmethod,
      items: [
        {
          description: data.paymentname,
          price: data.amount_cent / 100, // Convert cents to euros
          discount: 0, // Assuming no discount for simplicity
        },
      ],
    } as PurchaseData;
  } catch (e) {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Error fetching ordine ricevuta data: " + (e as Error).message,
    });
  }
};
