import type { Annunci, AnnunciId } from "~/schemas/public/Annunci";
import { TRPCError } from "@trpc/server";
import { createSrc, createSrcset } from "~/server/services/imageServer";
import { AnnuncioObjectWithImages } from "~/server/services/annunci.service";
import { zAnnuncioId } from "~/server/utils/zod_types";
import { z } from "zod";
import { db } from "~/server/db";
import type { ServizioServizioId } from "~/schemas/public/Servizio";

// const ratelimit = new RateLimiterHandler({
//   windowSize: 10,
//   maxRequests: 10,
//   analytics: true,
// });

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 {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore query getAllRaw",
    });
  }
};

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 {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore query getCodici",
    });
  }
};

export type AnnunciWithCoverImage = Annunci & { coverImage: string | null };
export const getAnnunciByCod = async ({
  cod,
}: {
  cod: string;
}): Promise<AnnunciWithCoverImage | null> => {
  try {
    const annuncio = await db
      .selectFrom("annunci")
      .selectAll()
      .where("annunci.web", "=", true)
      .where("codice", "=", cod)
      .executeTakeFirst();
    if (!annuncio) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Annuncio non trovato",
      });
    }
    const coverImage = annuncio.url_immagini
      ? annuncio.url_immagini[0]
        ? createSrc(annuncio.url_immagini[0])
        : null
      : null;

    const item = { ...annuncio, coverImage: coverImage };

    return AnnuncioObjectWithImages<typeof item>(item);
  } catch {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore query getAnnuncio",
    });
  }
};

export const getAnnunciById = async ({
  id,
}: {
  id: AnnunciId;
}): Promise<AnnunciWithCoverImage | null> => {
  try {
    const annuncio = await db
      .selectFrom("annunci")
      .selectAll()
      .where("id", "=", id)
      .executeTakeFirst();
    if (!annuncio) {
      throw new TRPCError({
        code: "NOT_FOUND",
        message: "Annuncio non trovato",
      });
    }
    const coverImage = annuncio.url_immagini
      ? annuncio.url_immagini[0]
        ? createSrc(annuncio.url_immagini[0])
        : null
      : null;

    const item = { ...annuncio, coverImage: coverImage };

    return AnnuncioObjectWithImages<typeof item>(item);
  } catch {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore query getAnnuncioById",
    });
  }
};

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

export const get_AnnunciPositionsHandler = async ({
  tipologia,
  comune,
  consegna,
}: {
  tipologia?: string;
  comune?: string;
  consegna?: number;
}): Promise<AnnuncioRicercaWPosition[]> => {
  try {
    let query = db
      .selectFrom("annunci")
      .select([
        "id",
        "codice",
        "comune",
        "provincia",
        "prezzo",
        "consegna",
        "numero_camere",
        "mq",
        "tipo",
        "titolo_it",
        "titolo_en",
        "url_immagini",
        "modificato_il",
        "stato",
        "lon_secondario",
        "lat_secondario",
      ])

      .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", "=", consegna);
    }

    const annunci = await query.execute();
    if (!annunci) {
      return [];
    }
    for (const annuncio of annunci) {
      annuncio.url_immagini = annuncio.url_immagini
        ? createSrcset(annuncio.url_immagini)
        : [];
    }

    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"
  | "url_immagini"
  | "modificato_il"
  | "stato"
>;

export const getCursor_AnnunciHandler = async ({
  input,
}: {
  input: {
    limit?: number;
    cursor?: string;
    tipologia?: string;
    comune?: string;
    consegna?: number;
    sort?: "Recenti" | "Prezzo" | "Consegna" | "Mq";
  };
}): Promise<{
  annunci: AnnuncioRicerca[];
  nextCursor: string | undefined;
}> => {
  const limit = input.limit ?? 50;
  const [cursor, sortCursor] = input.cursor?.split("_") ?? [0, undefined];
  function sortType(sort: "Recenti" | "Prezzo" | "Consegna" | "Mq") {
    switch (sort) {
      case "Recenti":
        return { col: "modificato_il", opr: "desc" };
      case "Prezzo":
        return { col: "prezzo", opr: "asc" };
      case "Consegna":
        return { col: "consegna", opr: "asc" };
      case "Mq":
        return { col: "mq", opr: "asc" };
    }
  }
  try {
    let query = db
      .selectFrom("annunci")
      .select([
        "id",
        "codice",
        "comune",
        "provincia",
        "prezzo",
        "consegna",
        "numero_camere",
        "mq",
        "tipo",
        "titolo_it",
        "titolo_en",
        "url_immagini",
        "modificato_il",
        "stato",
      ])

      .where("web", "=", true)
      .where("stato", "!=", "Sospeso");
    if (sortCursor && input.sort !== undefined) {
      const { col: sortColumn, opr } = sortType(input.sort) as {
        col: keyof Annunci;
        opr: string;
      };
      query = query.where((eb) =>
        eb.or([
          eb.and([
            eb("id", ">=", (cursor as AnnunciId) ?? 0),
            eb(sortColumn, "=", sortCursor.toString()),
          ]),
          eb(sortColumn, opr === "desc" ? "<" : ">", sortCursor.toString()),
        ]),
      );
    } else {
      query = query.where("id", ">=", (cursor as AnnunciId) ?? 0);
    }

    if (input.tipologia && input.tipologia !== "") {
      query = query.where("tipo", "=", input.tipologia);
    }
    if (input.comune && input.comune !== "") {
      query = query.where("comune", "like", input.comune);
    }
    if (input.consegna) {
      query = query.where("consegna", "=", input.consegna);
    }
    if (input.sort) {
      switch (input.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;
      }
    }
    query = query.orderBy("id", "asc");

    const itemsraw = await query.limit(limit + 1).execute();
    const annunci = itemsraw;
    let nextCursor: string | undefined = undefined;

    if (annunci.length > limit) {
      const nextItem = annunci.pop(); // return the last item from the array
      if (!nextItem) {
        throw new TRPCError({
          code: "INTERNAL_SERVER_ERROR",
          message: "Errore interno",
        });
      }
      if (!input.sort) {
        nextCursor = nextItem.id.toString();
      } else {
        const { col: sortColumn } = sortType(input.sort);
        if (sortColumn === "modificato_il") {
          if (!nextItem.modificato_il) {
            nextCursor = `${nextItem.id.toString()}_1990-01-01`;
          } else {
            nextCursor = `${nextItem.id.toString()}_${nextItem.modificato_il.toISOString()}`;
          }
        } else {
          nextCursor = `${nextItem.id.toString()}_${nextItem[sortColumn as keyof typeof nextItem]?.toString()}`;
        }
      }
    }

    for (const annuncio of annunci) {
      annuncio.url_immagini = annuncio.url_immagini
        ? createSrcset(annuncio.url_immagini)
        : [];
    }
    return {
      annunci,
      nextCursor,
    };
  } catch {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore query getWithCursor",
    });
  }
};

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

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

    return results;
  } catch {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore query getComuni",
    });
  }
};

export const EdiAnnuncioSchema = z.object({
  annuncioId: zAnnuncioId,
  titolo_it: z.string().nonempty("Inserisci un titolo valido"),
  desc_it: z.string().nonempty("Inserisci una descrizione valida"),
  titolo_en: z.string().nonempty("Inserisci un titolo valido"),
  desc_en: z.string().nonempty("Inserisci una descrizione valida"),
  status: z.string().nonempty("Seleziona uno stato"),
});

type EditAnnuncioFormValues = z.infer<typeof EdiAnnuncioSchema>;

export const editAnnuncioHandler = async ({
  annuncioId,
  titolo_it,
  desc_it,
  titolo_en,
  desc_en,
  status,
}: EditAnnuncioFormValues) => {
  try {
    await db
      .updateTable("annunci")
      .set({
        titolo_it: titolo_it,
        desc_it: desc_it,
        titolo_en: titolo_en,
        desc_en: desc_en,
        stato: status,
      })
      .where("id", "=", annuncioId)
      .execute();
    return true;
  } catch {
    throw new TRPCError({
      code: "INTERNAL_SERVER_ERROR",
      message: "Errore interno",
    });
  }
};

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 || !opened_at.open_contatti_at) {
      return { propData: null, opened_at: null };
    }

    const propData = await db
      .selectFrom("annunci")
      .select([
        "codice",
        "comune",
        "indirizzo",
        "civico",
        "provincia",
        "regione",
        "cap",
        "lat",
        "lon",
        "numero",
        "locatore",
      ])
      .where("id", "=", annuncioId)
      .executeTakeFirstOrThrow();

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