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

import { PROVIDERS_DE_TELEFONE, transportaMensagem } from "@/lib/channels/capabilities";
import { canalAceitaTextoLivreAgora } from "@/lib/channels/janela";

export interface CanalDoLembrete {
  id: string;
  provider: string | null;
  status: string;
  archived_at: string | null;
  metadata?: { disabled?: boolean } | null;
  lastInboundAt: string | null;
}

export type MotivoSemRemetente =
  | "falha_ao_ler_remetente"
  | "conversa_do_compromisso_invalida"
  | "canal_indicado_indisponivel"
  | "canal_fora_da_janela_24h"
  | "sem_canal"
  | "remetente_ambiguo";
export type RemetenteDoLembrete =
  | { canal: CanalDoLembrete; motivo: null }
  | {
      canal: null;
      motivo: MotivoSemRemetente;
      /**
       * Só com `canal_fora_da_janela_24h`, e só quando o remetente seria UM número
       * certo (o indicado, ou o único que a preferência deixa): é por ele que um
       * modelo aprovado ainda pode sair (`reminder_meta_template_id` do tipo,
       * `lib/agenda/lembrete-por-modelo.ts`). Texto livre continua proibido — este
       * campo não autoriza nada sozinho.
       */
      foraDaJanela?: CanalDoLembrete;
    };

/** Um vínculo é autoridade, não preferência: indisponível nunca libera outro número. */
export function decidirRemetenteDoLembrete(
  canais: CanalDoLembrete[],
  canalIndicadoId: string | null,
  agora: Date,
  telefoneDoContato?: string | null,
): RemetenteDoLembrete {
  const operante = (c: CanalDoLembrete) =>
    c.status === "WORKING" &&
    !c.archived_at &&
    c.metadata?.disabled !== true &&
    transportaMensagem(c.provider);
  if (canalIndicadoId) {
    const canal = canais.find((c) => c.id === canalIndicadoId);
    if (!canal || !operante(canal)) return { canal: null, motivo: "canal_indicado_indisponivel" };
    return canalAceitaTextoLivreAgora(canal.provider, canal.lastInboundAt, agora)
      ? { canal, motivo: null }
      : { canal: null, motivo: "canal_fora_da_janela_24h", foraDaJanela: canal };
  }

  // #2594: o canal do próprio contato — com telefone, o de telefone; sem, o
  // social. A preferência só desempata: se nenhum da lista é do tipo preferido,
  // a lista vale como antes. Telefone não informado (undefined) não inventa
  // preferência.
  const preferir = (lista: CanalDoLembrete[]) => {
    if (telefoneDoContato === undefined) return lista;
    const temTelefone = Boolean(telefoneDoContato?.trim());
    const preferidos = lista.filter(
      (c) => (PROVIDERS_DE_TELEFONE as readonly string[]).includes(c.provider ?? "") === temTelefone,
    );
    return preferidos.length > 0 ? preferidos : lista;
  };
  const operantes = canais.filter(operante);
  const candidatos = preferir(
    operantes.filter((c) => canalAceitaTextoLivreAgora(c.provider, c.lastInboundAt, agora)),
  );
  if (candidatos.length > 1) return { canal: null, motivo: "remetente_ambiguo" };
  const unico = candidatos[0];
  if (candidatos.length === 1 && unico) return { canal: unico, motivo: null };
  if (operantes.length === 0) return { canal: null, motivo: "sem_canal" };
  // Todos os operantes estão fora da janela. Se a mesma preferência deixa UM
  // número, é por ele que um modelo aprovado ainda pode sair; com dois, nenhum é
  // "o" remetente — e o modelo também não escolhe.
  const foraDaJanela = preferir(operantes);
  return foraDaJanela.length === 1 && foraDaJanela[0]
    ? { canal: null, motivo: "canal_fora_da_janela_24h", foraDaJanela: foraDaJanela[0] }
    : { canal: null, motivo: "canal_fora_da_janela_24h" };
}

/** Leitura sempre recortada à organização E ao contato da reserva. Falha não é ausência. */
export async function resolverRemetenteDoLembrete(
  admin: SupabaseClient,
  compromisso: { organization_id: string; contact_id: string; conversation_id?: string | null },
  canalDoTipoId: string | null,
  agora: Date,
  telefoneDoContato?: string | null,
): Promise<RemetenteDoLembrete> {
  const org = compromisso.organization_id;
  let indicado = canalDoTipoId;
  if (!indicado && compromisso.conversation_id) {
    const { data, error } = await admin
      .from("conversations")
      .select("channel_session_id")
      .eq("id", compromisso.conversation_id)
      .eq("organization_id", org)
      .eq("contact_id", compromisso.contact_id)
      .maybeSingle();
    if (error) return { canal: null, motivo: "falha_ao_ler_remetente" };
    if (!data?.channel_session_id)
      return { canal: null, motivo: "conversa_do_compromisso_invalida" };
    indicado = data.channel_session_id;
  }
  // O automático lê os candidatos e confere a contagem total: uma lista
  // truncada não pode provar que só existe um remetente.
  let query = admin
    .from("channel_sessions")
    .select("id, provider, status, archived_at, metadata", { count: "exact" })
    .eq("organization_id", org);
  if (indicado) query = query.eq("id", indicado);
  else query = query.eq("status", "WORKING").is("archived_at", null);
  const { data, error, count } = await query;
  if (error) return { canal: null, motivo: "falha_ao_ler_remetente" };
  // PostgREST pode cortar por max_rows. Lista parcial nunca prova remetente único.
  if (!indicado && count != null && count > (data?.length ?? 0)) {
    return { canal: null, motivo: "remetente_ambiguo" };
  }
  const canais: CanalDoLembrete[] = (data ?? []).map((c) => ({ ...c, lastInboundAt: null }));
  const restritos = canais.filter(
    (c) => transportaMensagem(c.provider) && !canalAceitaTextoLivreAgora(c.provider, null, agora),
  );
  if (restritos.length) {
    const { data: conversas, error: erro } = await admin
      .from("conversations")
      .select("channel_session_id, last_inbound_at")
      .eq("organization_id", org)
      .eq("contact_id", compromisso.contact_id)
      .in(
        "channel_session_id",
        restritos.map((c) => c.id),
      );
    if (erro) return { canal: null, motivo: "falha_ao_ler_remetente" };
    for (const conversa of conversas ?? []) {
      const canal = canais.find((c) => c.id === conversa.channel_session_id);
      if (
        canal &&
        conversa.last_inbound_at &&
        (!canal.lastInboundAt || conversa.last_inbound_at > canal.lastInboundAt)
      ) {
        canal.lastInboundAt = conversa.last_inbound_at;
      }
    }
  }
  return decidirRemetenteDoLembrete(canais, indicado, agora, telefoneDoContato);
}

