# app/services/notificacao/despachante.rb
#
# Entrega UMA mensagem já montada aos destinatários de um evento.
#
# Separação proposital: o Despachante resolve QUEM recebe, por qual canal,
# respeita o intervalo entre envios e registra o resultado. O TEXTO vem do
# template de blocos do evento (MensagemTemplate), renderizado por canal — HTML
# no e-mail, texto puro no WhatsApp, do mesmo template.
#
# `corpo:`/`assunto:` são o FALLBACK: valem quando o evento ainda não tem
# template montado (o texto padrão do código) ou quando a mensagem é digitada na
# hora (disparo manual). Sem esse fallback, ligar o editor apagaria as
# notificações dos eventos que já funcionavam.
#
# Destinatários = contatos dos grupos que assinam o evento
#                 + (opcional) a pessoa diretamente envolvida no fato, que é o
#                   comportamento que já existia: o motorista dono do pagamento.
#
# ⚠️ INTERVALO: no WhatsApp por sessão QR (canal não oficial), disparar em
#    rajada para dezenas de números é a forma mais rápida de perder o número.
#    Por isso o envio roda em job, com pausa configurável entre mensagens —
#    nunca dentro da requisição web.
module Notificacao
  class Despachante
    # Dispara em background. Nunca levanta: uma notificação não pode derrubar o
    # fechamento de um pagamento (mesma regra do NotificacaoService).
    # Dispara UM evento específico (disparo manual pelo botão da tela).
    def self.disparar(chave:, dados: {}, assunto: nil, corpo: nil, envolvido: nil)
      evento = EventoNotificacao.ativos.find_by(chave: chave.to_s)
      return if evento.nil?

      agendar(evento, dados: dados, assunto: assunto, corpo: corpo, envolvido: envolvido)
    rescue StandardError => e
      Rails.logger.error("[Despachante] falha ao agendar #{chave}: #{e.class}: #{e.message}")
    end

    # Dispara TODOS os eventos ativos de um gatilho.
    #
    # É o que o código de negócio usa. Buscar pela `chave` (como era antes) fazia
    # só o evento de SISTEMA disparar: um evento criado pelo ADM com o mesmo
    # gatilho ficava mudo para sempre, sem erro nenhum — a tela oferecia o
    # gatilho e nada acontecia. Com N eventos no mesmo gatilho, cada um tem seus
    # próprios grupos e sua própria mensagem, que é o ponto de poder cadastrar.
    def self.disparar_gatilho(gatilho:, dados: {}, assunto: nil, corpo: nil, envolvido: nil)
      EventoNotificacao.ativos.where(gatilho: gatilho.to_s).find_each do |evento|
        agendar(evento, dados: dados, assunto: assunto, corpo: corpo, envolvido: envolvido)
      end
    rescue StandardError => e
      Rails.logger.error("[Despachante] falha ao agendar gatilho #{gatilho}: #{e.class}: #{e.message}")
    end

    def self.agendar(evento, dados:, assunto:, corpo:, envolvido:)
      NotificacaoJob.perform_later(evento.id, (dados || {}).stringify_keys,
                                   assunto.to_s, corpo.to_s, envolvido&.id)
    end
    private_class_method :agendar

    def initialize(evento, dados: {}, assunto: nil, corpo: nil, envolvido: nil)
      @evento    = evento
      @dados     = (dados || {}).stringify_keys
      @assunto   = assunto.presence || evento.nome
      @corpo     = corpo.to_s
      @envolvido = envolvido
      @config    = ConfiguracaoNotificacao.instancia
    end

    def executar
      enviar_whatsapp if @config.whatsapp_habilitado?
      enviar_email    if @config.email_habilitado?
    end

    private

    def enviar_whatsapp
      intervalo = @config.intervalo_envio

      destinos_whatsapp.each_with_index do |(destino, contato, user), i|
        # Pausa ENTRE mensagens, não antes da primeira.
        sleep(intervalo) if i.positive? && intervalo.positive?

        corpo = corpo_whatsapp(contato, user)
        envio = registrar(canal: 'whatsapp', destino: destino, contato: contato,
                          user: user, corpo: corpo)
        resposta = Whatsapp.enviar(para: destino, texto: corpo, config: @config)
        resposta.ok? ? envio.marcar_enviado! : envio.marcar_falha!(resposta.erro)
      end
    end

    # ── Corpo por canal ─────────────────────────────────────────
    # A renderização acontece POR DESTINATÁRIO, não uma vez só: {{contato}} é o
    # primeiro nome de quem recebe, então um render compartilhado mandaria o
    # nome da primeira pessoa para todo mundo. Renderizar é manipulação de
    # string — o custo por destinatário é irrelevante perto do envio em si.
    def dados_para(contato, user)
      nome = contato&.primeiro_nome.presence || user&.nome.to_s.split.first
      dados_completos.merge('contato' => nome.to_s)
    end

    # O contexto do gatilho + as variáveis que o TEMPLATE usa e o gatilho não
    # conhece ({{entregas_mes}}, {{telefone_suporte}}, {{hora}}…).
    #
    # Resolvido UMA vez por disparo, não por destinatário: `{{entregas_mes}}` é
    # uma query, e o valor é o mesmo para todo mundo — só `{{contato}}` muda.
    def dados_completos
      @dados_completos ||= ResolvedorVariaveis.completar(textos_do_disparo, @dados)
    end

    # Tudo que será interpolado: os blocos dos dois templates (o evento pode ter
    # WhatsApp e e-mail com textos diferentes), o assunto e o corpo padrão.
    def textos_do_disparo
      [@assunto, @corpo,
       @evento.template_utilizavel('whatsapp')&.blocos,
       @evento.template_utilizavel('email')&.blocos,
       @evento.template_utilizavel('email')&.assunto]
    end

    # O template do editor vence; sem template utilizável, cai no texto que o
    # chamador passou (texto padrão do código ou mensagem digitada na hora).
    def corpo_whatsapp(contato, user)
      template = @evento.template_utilizavel('whatsapp')
      return @corpo unless template

      template.renderizador(dados_para(contato, user)).texto.presence || @corpo
    end

    # [assunto, corpo, html?] — com template o e-mail sai em HTML montado; sem
    # template, texto puro que o mailer formata.
    def email_montado(contato, user)
      template = @evento.template_utilizavel('email')
      return [@assunto, @corpo, false] unless template

      r = template.renderizador(dados_para(contato, user))
      [r.interpolar(template.assunto).presence || @assunto, r.html, true]
    end

    def enviar_email
      destinos_email.each do |destino, contato, user|
        assunto, corpo, html = email_montado(contato, user)
        envio = registrar(canal: 'email', destino: destino, contato: contato,
                          user: user, corpo: corpo, assunto: assunto)
        begin
          NotificacaoMailer.mensagem(destino, assunto, corpo, html: html).deliver_now
          envio.marcar_enviado!
        rescue StandardError => e
          envio.marcar_falha!("#{e.class}: #{e.message}")
        end
      end
    end

    # [[destino, contato, user], ...] sem repetir o mesmo número/e-mail — a mesma
    # pessoa pode ser contato de um grupo E o envolvido no fato.
    def destinos_whatsapp
      # `destino_whatsapp` resolve pessoa (número) ou grupo (JID) — ver Contato.
      lista = @evento.destinatarios('whatsapp').map { |c| [c.destino_whatsapp, c, c.user] }
      lista += envolvido_whatsapp
      dedup(lista)
    end

    def destinos_email
      lista = @evento.destinatarios('email').map { |c| [c.email, c, c.user] }
      lista += envolvido_email
      dedup(lista)
    end

    def envolvido_whatsapp
      return [] unless notificar_envolvido?

      numero = ConfiguracaoNotificacao.normalizar_telefone(@envolvido.telefone)
      numero.present? ? [[numero, nil, @envolvido]] : []
    end

    def envolvido_email
      return [] unless notificar_envolvido? && @envolvido.email.present?

      [[@envolvido.email, nil, @envolvido]]
    end

    def notificar_envolvido?
      @envolvido.present? && @evento.notificar_envolvido?
    end

    def dedup(lista)
      lista.reject { |destino, _, _| destino.blank? }
           .uniq    { |destino, _, _| destino.to_s.downcase }
    end

    def registrar(canal:, destino:, contato:, user:, corpo:, assunto: nil)
      NotificacaoEnvio.create!(
        evento_notificacao: @evento, contato: contato, user: user,
        canal: canal, destino: destino, assunto: assunto,
        corpo: corpo, status: 'pendente'
      )
    end
  end
end
