import type { SupabaseClient } from "@supabase/supabase-js";
import webpush from "web-push";

import { validar } from "@/lib/agenda/validacao";

import { dataBrasileira, diaDoCompromisso, type Acao, type Alvo } from "../regras";
import type { Empresa } from "./avaliar";

/**
 * O que a regra faz, quando dispara. Só ações seguras: compromisso na agenda,
 * aviso para alguém da equipe e etiqueta no cliente. Nada vai ao cliente da
 * empresa e nada mexe em dinheiro.
 *
 * Roda com a chave de serviço (a fila não tem ninguém logado), então toda
 * gravação aqui amarra a empresa à mão: company_id = dono, cliente dentro dos
 * ids da empresa, pessoa que pertence à equipe.
 */

type Contexto = {
  admin: SupabaseClient;
  empresa: Empresa;
  regra: { id: string; nome: string };
  /* Quem pode receber compromisso e aviso (o dono e a equipe ativa). */
  pessoas: Set<string>;
  /* A categoria de compromisso da empresa, lida uma vez por volta. */
  categoria: () => Promise<string | null>;
};

export type ResultadoDaAcao = { ok: boolean; texto: string };

export function categoriaDaEmpresa(admin: SupabaseClient, dono: string) {
  let lida: Promise<string | null> | null = null;
  return () => {
    lida ??= (async () => {
      const { data } = await admin
        .from("agenda_categories")
        .select("id")
        .eq("company_id", dono)
        .eq("is_system", false)
        .order("sort_order")
        .limit(1);
      return ((data ?? []) as Array<{ id: string }>)[0]?.id ?? null;
    })();
    return lida;
  };
}

function quemRecebe(ctx: Contexto, id: string | null) {
  /* Pessoa que saiu da equipe: o compromisso vai para o dono, nunca para fora. */
  return id && ctx.pessoas.has(id) ? id : ctx.empresa.dono;
}

async function marcarNaAgenda(
  ctx: Contexto,
  alvo: Alvo,
  dados: { titulo: string; daquiADias: number; lembrete: number | null; responsavel: string }
): Promise<ResultadoDaAcao> {
  const categoria = await ctx.categoria();
  if (!categoria) return { ok: false, texto: "a agenda não tem categoria de compromisso (crie uma em Agenda)" };

  let clienteId: string | null = null;
  if (alvo.clienteId) {
    const { data } = await ctx.admin.from("clients").select("id").eq("id", alvo.clienteId).in("user_id", ctx.empresa.tenantIds).maybeSingle();
    clienteId = data ? alvo.clienteId : null;
  }

  const conferido = validar({
    title: dados.titulo.slice(0, 200),
    description: `${alvo.detalhe}\n\nMarcado pela regra "${ctx.regra.nome}".`.slice(0, 2000),
    event_date: diaDoCompromisso(dados.daquiADias),
    client_id: clienteId,
    responsible_user_id: dados.responsavel,
    category_id: categoria,
    status: "agendado",
    recurrence: "none",
    lembrete_minutos: dados.lembrete,
  });
  if (!conferido.ok) return { ok: false, texto: `compromisso recusado: ${conferido.erro}` };

  const { error } = await ctx.admin.from("agenda_events").insert({
    ...conferido.dados,
    company_id: ctx.empresa.dono,
    created_by: ctx.empresa.dono,
    order_id: alvo.pedidoId,
    quote_id: alvo.orcamentoId,
  });
  if (error) {
    console.error("[regras] compromisso não gravou", error.message);
    return { ok: false, texto: "o compromisso não foi gravado" };
  }
  return { ok: true, texto: `compromisso marcado para ${dataBrasileira(conferido.dados.event_date)}` };
}

function configurarPush() {
  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;
}

/* O aviso no celular/computador de quem ligou as notificações da agenda. */
async function avisarNoAparelho(ctx: Contexto, usuario: string, titulo: string, corpo: string, rota: string): Promise<number> {
  if (!configurarPush()) return 0;
  const { data } = await ctx.admin
    .from("push_subscriptions")
    .select("id,endpoint,p256dh,auth,failures")
    .eq("user_id", usuario)
    .in("company_id", ctx.empresa.tenantIds)
    .lt("failures", 5)
    .limit(10);
  let enviados = 0;
  for (const a of (data ?? []) as Array<{ id: string; endpoint: string; p256dh: string; auth: string; failures: number | null }>) {
    try {
      await webpush.sendNotification(
        { endpoint: a.endpoint, keys: { p256dh: a.p256dh, auth: a.auth } },
        JSON.stringify({ titulo, corpo, rota, tag: `regra-${ctx.regra.id}` })
      );
      enviados += 1;
      await ctx.admin.from("push_subscriptions").update({ last_sent_at: new Date().toISOString(), failures: 0 }).eq("id", a.id);
    } catch (erro) {
      const status = (erro as { statusCode?: number })?.statusCode;
      if (status === 404 || status === 410) await ctx.admin.from("push_subscriptions").delete().eq("id", a.id);
      else await ctx.admin.from("push_subscriptions").update({ failures: Number(a.failures ?? 0) + 1 }).eq("id", a.id);
    }
  }
  return enviados;
}

async function porEtiqueta(ctx: Contexto, alvo: Alvo, etiqueta: string): Promise<ResultadoDaAcao> {
  if (!alvo.clienteId) return { ok: true, texto: "sem cliente cadastrado para a etiqueta" };
  const { data, error } = await ctx.admin
    .from("clients")
    .select("id,tags")
    .eq("id", alvo.clienteId)
    .in("user_id", ctx.empresa.tenantIds)
    .maybeSingle();
  if (error || !data) return { ok: false, texto: "cliente não encontrado para a etiqueta" };
  const atuais = Array.isArray((data as { tags?: unknown }).tags) ? ((data as { tags: unknown[] }).tags.map(String)) : [];
  if (atuais.some((t) => t.trim().toLowerCase() === etiqueta.toLowerCase())) return { ok: true, texto: `o cliente já tinha a etiqueta "${etiqueta}"` };
  const { error: erroAoGravar } = await ctx.admin
    .from("clients")
    .update({ tags: [...atuais, etiqueta].slice(0, 50) })
    .eq("id", alvo.clienteId)
    .in("user_id", ctx.empresa.tenantIds);
  if (erroAoGravar) {
    console.error("[regras] etiqueta não gravou", erroAoGravar.message);
    return { ok: false, texto: "a etiqueta não foi gravada" };
  }
  return { ok: true, texto: `etiqueta "${etiqueta}" posta no cliente` };
}

export async function executarAcao(ctx: Contexto, acao: Acao, alvo: Alvo): Promise<ResultadoDaAcao> {
  try {
    switch (acao.tipo) {
      case "criar_compromisso": {
        const titulo = acao.parametros.titulo ? `${acao.parametros.titulo}: ${alvo.titulo}` : alvo.titulo;
        return await marcarNaAgenda(ctx, alvo, {
          titulo,
          daquiADias: acao.parametros.daqui_a_dias,
          lembrete: acao.parametros.lembrete_minutos,
          responsavel: quemRecebe(ctx, acao.parametros.responsavel_id),
        });
      }
      case "avisar_pessoa": {
        /* Não existe caixa de avisos gravada no painel (o sino é montado na
           hora, de outras tabelas). O aviso fica como compromisso de hoje na
           agenda da pessoa, e vai ao aparelho dela se as notificações estiverem ligadas. */
        const pessoa = quemRecebe(ctx, acao.parametros.usuario_id);
        const titulo = `Aviso: ${acao.parametros.mensagem ? `${acao.parametros.mensagem} (${alvo.titulo})` : alvo.titulo}`;
        const naAgenda = await marcarNaAgenda(ctx, alvo, { titulo, daquiADias: 0, lembrete: null, responsavel: pessoa });
        const enviados = await avisarNoAparelho(ctx, pessoa, acao.parametros.mensagem || "Regra disparou", alvo.titulo, alvo.rota).catch(() => 0);
        return {
          ok: naAgenda.ok,
          texto: naAgenda.ok
            ? `aviso na agenda de hoje${enviados ? ` e no celular (${enviados})` : ""}`
            : naAgenda.texto,
        };
      }
      case "adicionar_etiqueta_ao_cliente":
        return await porEtiqueta(ctx, alvo, acao.parametros.etiqueta);
    }
  } catch (erro) {
    console.error("[regras] ação falhou", erro instanceof Error ? erro.message : erro);
    return { ok: false, texto: "a ação falhou" };
  }
}

export type { Contexto as ContextoDaAcao };
