import { NextResponse } from "next/server";
import { avisarConversa, canalDaLoja } from "@/lib/chats/aviso";
import { createAdminClient } from "@/lib/supabase-server";
import webpush from "web-push";

/*
  O relógio do atendimento.

  Roda de poucos em poucos minutos e faz duas coisas que ninguém faz sozinho:

  - **cutuca a conversa parada**: alguém escreveu, ninguém respondeu e já passou
    o tempo que a loja escolheu. Sem isto, "sem resposta" só aparecia num
    relatório que ninguém abre — e a pessoa do outro lado já tinha perguntado
    em outro lugar;
  - **devolve o que ficou de retomar**: o orçamento que morre no silêncio morre
    porque ninguém lembrou de voltar nele. A conversa volta ao topo no dia
    marcado, com o sino tocando.

  Avisa **uma vez** por conversa: marca antes de mandar, como o lembrete da
  agenda. Repetição é o que faz alguém desligar a notificação.

  Fora do expediente ninguém é cutucado — cobrar resposta às 3 da manhã é só
  ruído, e a loja já avisou o cliente que responde amanhã.
*/

export const dynamic = "force-dynamic";
export const maxDuration = 60;

/* Quantas conversas o relógio olha por rodada. Mais do que isso é fila
   acumulada, e aí o problema não é de aviso. */
const LIMITE = 80;

function texto(valor: unknown) {
  return String(valor ?? "").trim();
}

function configurar() {
  const publica = process.env.NEXT_PUBLIC_VAPID_PUBLIC_KEY || process.env.VAPID_PUBLIC_KEY;
  const privada = process.env.VAPID_PRIVATE_KEY;
  const contato = process.env.VAPID_SUBJECT || "mailto:contato@ktalogu.com.br";
  if (!publica || !privada) return false;
  webpush.setVapidDetails(contato, publica, privada);
  return true;
}

/* Fuso de Brasília: o servidor roda em UTC e o expediente é o da loja. */
const FUSO = "America/Sao_Paulo";
const DIAS = ["dom", "seg", "ter", "qua", "qui", "sex", "sab"] as const;

function dentroDoExpediente(horario: unknown) {
  if (!Array.isArray(horario) || !horario.length) return true;

  const agora = new Date();
  const partes = new Intl.DateTimeFormat("pt-BR", {
    timeZone: FUSO,
    hour: "2-digit",
    minute: "2-digit",
    hour12: false,
  }).formatToParts(agora);
  const pegar = (tipo: string) => partes.find((parte) => parte.type === tipo)?.value || "00";
  const hhmm = `${pegar("hour").padStart(2, "0")}:${pegar("minute").padStart(2, "0")}`;
  const indice = new Date(agora.toLocaleString("en-US", { timeZone: FUSO })).getDay();

  const faixa = (horario as { dia: string; aberto: boolean; de: string; ate: string }[]).find(
    (item) => item?.dia === DIAS[indice]
  );
  if (!faixa?.aberto) return false;
  const de = texto(faixa.de) || "00:00";
  const ate = texto(faixa.ate) || "23:59";
  return de <= ate ? hhmm >= de && hhmm <= ate : hhmm >= de || hhmm <= ate;
}

/* O aviso vai para os mesmos aparelhos da agenda: quem ligou uma vez recebe
   tudo. */
async function avisarAparelhos(
  admin: ReturnType<typeof createAdminClient>,
  tenantId: string,
  aviso: { titulo: string; corpo: string; rota: string; tag: string }
) {
  const { data: aparelhos } = await admin
    .from("push_subscriptions")
    .select("id,endpoint,p256dh,auth,failures")
    .eq("company_id", tenantId)
    .lt("failures", 5)
    .limit(50);

  let enviados = 0;
  for (const aparelho of aparelhos ?? []) {
    try {
      await webpush.sendNotification(
        {
          endpoint: String(aparelho.endpoint),
          keys: { p256dh: String(aparelho.p256dh), auth: String(aparelho.auth) },
        },
        JSON.stringify(aviso)
      );
      enviados += 1;
    } catch (erro) {
      const status = (erro as { statusCode?: number })?.statusCode;
      if (status === 404 || status === 410) {
        await admin.from("push_subscriptions").delete().eq("id", aparelho.id);
      } else {
        await admin
          .from("push_subscriptions")
          .update({ failures: Number(aparelho.failures ?? 0) + 1 })
          .eq("id", aparelho.id);
      }
    }
  }
  return enviados;
}

export async function POST(req: Request) {
  /* Mesma porta e mesmo segredo do lembrete da agenda: quem chama é o
     agendador, que não tem sessão. */
  const segredo = process.env.AGENDA_PUSH_SECRET;
  if (!segredo) return NextResponse.json({ error: "AGENDA_PUSH_SECRET nao configurado." }, { status: 500 });
  if (req.headers.get("x-agenda-secret") !== segredo) {
    return NextResponse.json({ error: "Nao autorizado." }, { status: 401 });
  }

  const admin = createAdminClient();
  const temPush = configurar();
  const agora = new Date();

  /*
    De passagem, o Pix do balcão que venceu sem ninguém perguntar por ele: o
    vencimento só era gravado quando a tela consultava a cobrança, então um QR
    abandonado ficava "pendente" para sempre. Dois minutos de folga para o
    aviso do provedor chegar antes.
  */
  try {
    await admin
      .from("pdv_pix_charges")
      .update({ status: "expired", cancelled_at: agora.toISOString(), updated_at: agora.toISOString() })
      .eq("status", "pending")
      .lt("expires_at", new Date(agora.getTime() - 2 * 60 * 1000).toISOString());
  } catch {
    /* faxina; o relógio do atendimento segue */
  }

  /* As lojas que atendem pelo chat: as outras não têm o que cobrar. */
  const { data: perfis } = await admin
    .from("profiles")
    .select("id,catalog_name,store_name,chat_nudge_minutes,chat_business_hours,store_chat_enabled")
    .eq("store_chat_enabled", true);

  const lojas = (perfis || []) as {
    id: string;
    catalog_name?: string | null;
    store_name?: string | null;
    chat_nudge_minutes?: number | null;
    chat_business_hours?: unknown;
  }[];

  let cutucadas = 0;
  let retomadas = 0;

  for (const loja of lojas) {
    const minutos = Number(loja.chat_nudge_minutes ?? 15);
    const noExpediente = dentroDoExpediente(loja.chat_business_hours);

    /* ---------------- conversa parada ---------------- */
    if (minutos > 0 && noExpediente) {
      const limite = new Date(agora.getTime() - minutos * 60 * 1000).toISOString();

      const { data: paradas } = await admin
        .from("chat_threads")
        .select("id,title,visitor_name,last_message_preview,last_message_at")
        .eq("user_id", loja.id)
        .in("kind", ["order", "quote", "client", "visitor"])
        .is("closed_at", null)
        .is("nudged_at", null)
        .gt("unread_store", 0)
        .lt("last_message_at", limite)
        .order("last_message_at", { ascending: true })
        .limit(LIMITE);

      const fila = (paradas || []) as {
        id: string;
        title: string | null;
        visitor_name: string | null;
        last_message_preview: string | null;
        last_message_at: string | null;
      }[];

      for (const conversa of fila) {
        /* Marca antes de mandar: se o envio demorar, a rodada seguinte não
           cutuca de novo pela mesma conversa. */
        await admin.from("chat_threads").update({ nudged_at: agora.toISOString() }).eq("id", conversa.id);

        const quem = texto(conversa.visitor_name) || texto(conversa.title) || "Alguém";
        const espera = conversa.last_message_at
          ? Math.round((agora.getTime() - new Date(conversa.last_message_at).getTime()) / 60000)
          : minutos;

        await avisarConversa([canalDaLoja(loja.id)], {
          threadId: conversa.id,
          autor: "system",
          autorNome: `${quem} está esperando`,
          resumo: `Sem resposta há ${espera} min · ${texto(conversa.last_message_preview).slice(0, 70)}`,
        });

        if (temPush) {
          await avisarAparelhos(admin, loja.id, {
            titulo: `${quem} está esperando resposta`,
            corpo: `Há ${espera} minutos sem resposta.`,
            rota: `/chats?conversa=${conversa.id}`,
            tag: `esperando-${conversa.id}`,
          });
        }
        cutucadas += 1;
      }
    }

    /* ---------------- retomar ---------------- */
    const { data: aRetomar } = await admin
      .from("chat_threads")
      .select("id,title,visitor_name,follow_up_note,client_id")
      .eq("user_id", loja.id)
      .not("follow_up_at", "is", null)
      .lte("follow_up_at", agora.toISOString())
      .limit(LIMITE);

    const lembretes = (aRetomar || []) as {
      id: string;
      title: string | null;
      visitor_name: string | null;
      follow_up_note: string | null;
      client_id: string | null;
    }[];

    for (const conversa of lembretes) {
      /* O lembrete some assim que dispara: ele cumpriu o papel de trazer a
         conversa de volta, e quem decide o próximo passo é gente. */
      await admin.from("chat_threads").update({ follow_up_at: null }).eq("id", conversa.id);

      const quem = texto(conversa.visitor_name) || texto(conversa.title) || "Conversa";
      const nota = texto(conversa.follow_up_note) || "Você marcou para retomar esta conversa hoje.";

      await avisarConversa([canalDaLoja(loja.id)], {
        threadId: conversa.id,
        autor: "system",
        autorNome: `Retomar: ${quem}`,
        resumo: nota.slice(0, 90),
      });

      /* Fica escrito na conversa, só para a equipe: daqui a uma semana ninguém
         lembra por que aquilo voltou ao topo. */
      await admin.from("chat_messages").insert({
        thread_id: conversa.id,
        user_id: loja.id,
        author_type: "system",
        author_name: "Lembrete",
        body: `Hora de retomar: ${nota}`,
        internal: true,
        read_by_store_at: agora.toISOString(),
      });

      if (temPush) {
        await avisarAparelhos(admin, loja.id, {
          titulo: `Retomar: ${quem}`,
          corpo: nota.slice(0, 120),
          rota: `/chats?conversa=${conversa.id}`,
          tag: `retomar-${conversa.id}`,
        });
      }
      retomadas += 1;
    }
  }

  return NextResponse.json({ ok: true, cutucadas, retomadas, lojas: lojas.length });
}
