# app/models/evento_notificacao.rb
#
# Um evento que dispara mensagem. O ADM cadastra quantos quiser e liga cada um a
# grupos de contato — mas o GATILHO sai de GATILHOS, porque gatilho é código.
#
#   manual                  → botão "disparar agora" (aviso pontual)
#   consolidacao_finalizada → NotificacaoService.notificar_finalizacao
#   pagamento_efetuado      → NotificacaoService.notificar_pagamento
#   valor_alterado          → edição de lançamento / desconto após o fechamento
#   operacao_alterada       → NF que entrou/saiu, entrega que mudou de status
#   agendado                → resumo periódico (frequencia + hora)
#
# `sistema: true` marca os que nascem com o app: editáveis e desligáveis, mas
# não apagáveis — apagar deixaria o código disparando no vazio.
class EventoNotificacao < ApplicationRecord
  self.table_name = 'eventos_notificacao'

  GATILHOS = %w[manual consolidacao_finalizada pagamento_efetuado
                valor_alterado operacao_alterada agendado].freeze

  GATILHO_LABEL = {
    'manual'                  => 'Disparo manual (botão)',
    'consolidacao_finalizada' => 'Consolidação finalizada',
    'pagamento_efetuado'      => 'Pagamento efetuado',
    'valor_alterado'          => 'Valor alterado no fechamento',
    'operacao_alterada'       => 'Dados da operação mudaram',
    'agendado'                => 'Agendado (resumo periódico)'
  }.freeze

  FREQUENCIAS = %w[diaria semanal].freeze

  # Gatilhos que se referem a UMA pessoa específica (o motorista do pagamento),
  # e não só a grupos. Só neles `notificar_envolvido` faz sentido.
  GATILHOS_COM_ENVOLVIDO = %w[consolidacao_finalizada pagamento_efetuado valor_alterado].freeze

  has_many :assinaturas, class_name: 'GrupoEventoAssinatura', dependent: :destroy
  has_many :grupos_contato, through: :assinaturas, source: :grupo_contato
  has_many :notificacao_envios, dependent: :nullify
  has_many :mensagem_templates, dependent: :destroy

  accepts_nested_attributes_for :assinaturas, allow_destroy: true

  validates :nome, presence: true
  validates :chave, presence: true, uniqueness: { case_sensitive: false }
  validates :gatilho, inclusion: { in: GATILHOS }
  validates :frequencia, inclusion: { in: FREQUENCIAS }, if: :agendado?
  validates :hora, numericality: { only_integer: true, greater_than_or_equal_to: 0,
                                   less_than_or_equal_to: 23 }, allow_nil: true
  validates :dia_semana, numericality: { only_integer: true, greater_than_or_equal_to: 0,
                                         less_than_or_equal_to: 6 }, allow_nil: true
  validate :semanal_tem_dia

  before_validation :normalizar

  scope :ativos,      -> { where(ativo: true) }
  scope :ordenados,   -> { order(Arel.sql('LOWER(nome)')) }
  scope :do_gatilho,  ->(g) { ativos.where(gatilho: g) }

  def agendado? = gatilho == 'agendado'
  def manual?   = gatilho == 'manual'
  def envolvido_aplicavel? = GATILHOS_COM_ENVOLVIDO.include?(gatilho)
  def gatilho_label = GATILHO_LABEL[gatilho] || gatilho

  # Só pode apagar o que o ADM criou.
  def apagavel? = !sistema?

  # Template do canal, criando um vazio em memória quando ainda não existe —
  # assim a tela do editor abre igual para evento novo e evento já configurado.
  def template(canal)
    mensagem_templates.find { |t| t.canal == canal.to_s } ||
      mensagem_templates.build(canal: canal.to_s, blocos: [])
  end

  # O corpo configurado no editor vence o texto padrão do código. Um template
  # ativo porém VAZIO não conta (mandaria mensagem em branco).
  def template_utilizavel(canal)
    t = mensagem_templates.detect { |x| x.canal == canal.to_s }
    t&.utilizavel? ? t : nil
  end

  # Todas as variáveis do sistema, agrupadas por origem e já marcadas com
  # `aplicavel:` para este gatilho — a lista curta de antes obrigava deploy para
  # o ADM escrever qualquer número no texto.
  def variaveis = Notificacao::CatalogoVariaveis.por_origem(gatilho)

  # Só os nomes que ESTE gatilho preenche (usado pela invariante dos specs).
  def variaveis_do_contexto = Notificacao::Variaveis.nomes(gatilho)

  # Contatos que devem receber este evento, sem repetição — a mesma pessoa pode
  # estar em dois grupos assinantes.
  def destinatarios(canal)
    ids = assinaturas.where(ativo: true)
                     .select { |a| a.cobre_canal?(canal) }
                     .map(&:grupo_contato_id)
    return Contato.none if ids.empty?

    Contato.ativos.where(grupo_contato_id: ids).com_canal(canal).distinct
  end

  # Já passou da hora de disparar hoje/esta semana?
  def vencido?(agora = Time.current)
    return false unless agendado? && ativo?
    return false unless agora.hour >= hora.to_i
    return false if frequencia == 'semanal' && agora.wday != dia_semana.to_i
    return true  if ultimo_disparo_em.blank?

    ultimo_disparo_em < agora.beginning_of_day
  end

  private

  def normalizar
    self.nome  = nome.to_s.strip.squeeze(' ').presence
    self.chave = chave.presence || nome.to_s.parameterize(separator: '_').presence
    self.chave = chave.to_s.strip.downcase.presence
    self.dia_semana = nil unless agendado? && frequencia == 'semanal'
    self.frequencia = nil unless agendado?
  end

  def semanal_tem_dia
    return unless agendado? && frequencia == 'semanal' && dia_semana.blank?

    errors.add(:dia_semana, 'é obrigatório na frequência semanal')
  end
end
