# app/models/configuracao_notificacao.rb
#
# Configuração de envio (SMTP + WhatsApp/Twilio) editável pelo ADM na tela
# /admin/configuracao_notificacao. Tabela SINGLETON: sempre uma linha só.
#
# HIERARQUIA: banco > .env. Enquanto a tela não estiver preenchida e marcada
# como ativa, `smtp_settings` devolve nil e `credenciais_whatsapp` cai no ENV —
# ou seja, o comportamento é idêntico ao que existia antes desta tela.
class ConfiguracaoNotificacao < ApplicationRecord
  include AtributoCifrado

  self.table_name = 'configuracao_notificacoes'

  atributo_cifrado :smtp_password, :twilio_auth_token, :baileys_token

  AUTENTICACOES = %w[plain login cram_md5].freeze

  # Quem entrega o WhatsApp. `baileys` é a ponte por QR code (container
  # `whatsapp`), gratuita e ilimitada, porém NÃO OFICIAL — a Meta pode banir o
  # número. `twilio` é o canal oficial pago que existia antes.
  PROVEDORES_WHATSAPP = %w[baileys twilio].freeze
  BAILEYS_URL_PADRAO  = 'http://whatsapp:3001'.freeze
  EMAIL_RE      = /\A[^@\s]+@[^@\s]+\z/
  REMETENTE_PADRAO = 'noreply@gade.com.br'

  before_validation :normalizar

  validates :smtp_address,  presence: true, if: :smtp_ativo?
  validates :smtp_username, presence: true, if: :smtp_ativo?
  validates :smtp_port, numericality: { only_integer: true,
                                        greater_than: 0, less_than_or_equal_to: 65_535 }
  validates :smtp_autenticacao, inclusion: { in: AUTENTICACOES }
  validates :remetente_email, format: { with: EMAIL_RE }, allow_blank: true
  validates :email_admin,     format: { with: EMAIL_RE }, allow_blank: true
  validate  :senha_smtp_presente, if: :smtp_ativo?

  validates :whatsapp_provedor, inclusion: { in: PROVEDORES_WHATSAPP }
  validates :whatsapp_intervalo_segundos,
            numericality: { only_integer: true, greater_than_or_equal_to: 0,
                            less_than_or_equal_to: 300 }

  # As credenciais do Twilio só são exigidas quando ele É o provedor escolhido.
  validates :twilio_account_sid, presence: true, if: :twilio_exigido?
  validates :twilio_from,        presence: true, if: :twilio_exigido?
  validate  :token_twilio_presente, if: :twilio_exigido?
  validate  :baileys_configuravel,  if: :baileys_exigido?

  # ── Singleton ───────────────────────────────────────────────
  def self.instancia
    first || create!
  rescue ActiveRecord::RecordNotUnique
    # Dois workers Puma criando a primeira linha ao mesmo tempo.
    first
  rescue ActiveRecord::StatementInvalid,
         ActiveRecord::NoDatabaseError,
         ActiveRecord::ConnectionNotEstablished => e
    # Boot do container antes do db:migrate, assets:precompile, console sem banco…
    Rails.logger.warn("[ConfiguracaoNotificacao] tabela indisponível (#{e.class}) — usando .env")
    new
  end

  # ── Usados pelo ApplicationMailer: NUNCA podem estourar ─────
  # (um erro aqui derrubaria todo envio de e-mail, inclusive o reset de senha)

  def self.remetente
    instancia.remetente_formatado
  rescue StandardError => e
    Rails.logger.error("[ConfiguracaoNotificacao.remetente] #{e.class}: #{e.message}")
    ENV.fetch('SMTP_USERNAME', REMETENTE_PADRAO)
  end

  # nil => o ActionMailer faz `.merge({})` e mantém o que o
  # config/initializers/smtp.rb montou a partir do .env no boot.
  def self.opcoes_de_entrega
    instancia.smtp_settings
  rescue StandardError => e
    Rails.logger.error("[ConfiguracaoNotificacao.opcoes_de_entrega] #{e.class}: #{e.message}")
    nil
  end

  # ── SMTP ────────────────────────────────────────────────────
  def smtp_pronto?
    smtp_ativo? && smtp_address.present? && smtp_port.to_i.positive? &&
      smtp_username.present? && smtp_password.present?
  end

  def smtp_settings
    return nil unless smtp_pronto?

    porta = smtp_port.to_i
    base = {
      address:        smtp_address,
      port:           porta,
      user_name:      smtp_username,
      password:       smtp_password,
      domain:         smtp_domain.presence || dominio_padrao,
      authentication: (smtp_autenticacao.presence || 'plain').to_sym,
      # Sem timeout, uma porta filtrada pelo firewall do VPS pendura o worker
      # Puma até o Nginx cortar — o botão "enviar teste" ficaria girando.
      open_timeout:   10,
      read_timeout:   10
    }
    # 465 = SSL implícito; 587/25 = STARTTLS.
    base.merge(porta == 465 ? { ssl: true, tls: true } : { enable_starttls_auto: true })
  end

  def remetente_formatado
    endereco = remetente_email.presence ||
               smtp_username.presence ||
               ENV.fetch('SMTP_USERNAME', REMETENTE_PADRAO)
    remetente_nome.present? ? %("#{remetente_nome}" <#{endereco}>) : endereco
  end

  def email_habilitado?
    email_notificacoes_ativo?
  end

  # ── WhatsApp: escolha de provedor ───────────────────────────
  def baileys?  = whatsapp_provedor.to_s == 'baileys'
  def twilio?   = whatsapp_provedor.to_s == 'twilio'

  def twilio_exigido?  = whatsapp_ativo? && twilio?
  def baileys_exigido? = whatsapp_ativo? && baileys?

  # URL e token da ponte: banco > .env, mesma hierarquia do resto da tela.
  #
  # `WHATSAPP_URL` é o nome atual — a tela e o .env.example falam em "ponte do
  # WhatsApp", não no nome da biblioteca. `BAILEYS_URL` continua sendo lido para
  # não quebrar quem já tem a variável antiga no .env em produção.
  def baileys_url_efetiva
    baileys_url.presence || ENV['WHATSAPP_URL'].presence ||
      ENV['BAILEYS_URL'].presence || BAILEYS_URL_PADRAO
  end

  def baileys_token_efetivo
    baileys_token.presence || ENV['WHATSAPP_TOKEN'].presence
  end

  def baileys_pronto?
    whatsapp_ativo? && baileys? && baileys_token_efetivo.present?
  end

  # Segundos entre uma mensagem e a seguinte. Disparo em rajada para dezenas de
  # contatos é a forma mais rápida de o número ser banido no canal não oficial.
  def intervalo_envio
    whatsapp_intervalo_segundos.to_i.clamp(0, 300)
  end

  # ── WhatsApp / Twilio ───────────────────────────────────────
  def whatsapp_pronto?
    whatsapp_ativo? && twilio_account_sid.present? &&
      twilio_auth_token.present? && twilio_from.present?
  end

  def whatsapp_habilitado?
    return false unless whatsapp_ativo?

    baileys? ? baileys_pronto? : credenciais_whatsapp.present?
  end

  # Tudo-ou-nada: nunca mistura o SID do banco com o token do .env (a conta
  # seria outra e o Twilio devolveria 20003 sem explicação óbvia).
  def credenciais_whatsapp
    if whatsapp_pronto?
      { sid:   twilio_account_sid,
        token: twilio_auth_token,
        from:  self.class.canal(twilio_from) }
    elsif ENV['TWILIO_ACCOUNT_SID'].present? && ENV['TWILIO_AUTH_TOKEN'].present?
      { sid:   ENV['TWILIO_ACCOUNT_SID'],
        token: ENV['TWILIO_AUTH_TOKEN'],
        from:  self.class.canal(ENV['TWILIO_WHATSAPP_FROM']) }
    end
  end

  # ── Telefone ────────────────────────────────────────────────
  # "11 920051157", "(11) 92005-1157", "011 92005-1157", "+55 11 92005-1157"
  # e "whatsapp:+5511920051157" → "+5511920051157".
  def self.normalizar_telefone(valor)
    return nil if valor.blank?

    d = valor.to_s.gsub(/\D/, '').sub(/\A0+/, '')
    # Só tira o 55 quando sobra número demais — senão quebraria o DDD 55
    # (Santa Maria/RS), onde "55991234567" já é o número completo.
    d = d.sub(/\A55/, '') if d.length > 11 && d.start_with?('55')
    return nil unless d.length.between?(10, 11)

    "+55#{d}"
  end

  def self.canal(numero)
    e164 = normalizar_telefone(numero)
    return nil if e164.blank?

    "whatsapp:#{e164}"
  end

  private

  def dominio_padrao
    (remetente_email.presence || smtp_username).to_s.split('@').last.presence || 'localhost'
  end

  def normalizar
    self.smtp_address       = smtp_address.to_s.strip.presence
    self.smtp_username      = smtp_username.to_s.strip.presence
    self.smtp_domain        = smtp_domain.to_s.strip.presence
    self.remetente_email    = remetente_email.to_s.strip.downcase.presence
    self.remetente_nome     = remetente_nome.to_s.strip.presence
    self.email_admin        = email_admin.to_s.strip.downcase.presence
    self.twilio_account_sid = twilio_account_sid.to_s.strip.presence
    self.baileys_url        = baileys_url.to_s.strip.chomp('/').presence
    self.whatsapp_provedor  = whatsapp_provedor.presence || 'baileys'
    # Mantém o que o admin digitou se não der para normalizar — a validação de
    # presença acusa, e ele vê o próprio valor de volta no campo para corrigir.
    self.twilio_from    = self.class.normalizar_telefone(twilio_from) || twilio_from.presence
    self.whatsapp_admin = self.class.normalizar_telefone(whatsapp_admin) || whatsapp_admin.presence
  end

  def baileys_configuravel
    return if baileys_token_efetivo.present?

    errors.add(:baileys_token,
               'é obrigatório: defina WHATSAPP_TOKEN no .env (o mesmo do container `whatsapp`) ' \
               'ou preencha aqui.')
  end

  def senha_smtp_presente
    return if smtp_password.present?

    errors.add(:smtp_password,
               if smtp_password_cifrado.present?
                 'gravada está ilegível (o SECRET_KEY_BASE do servidor mudou?). Digite a senha novamente.'
               else
                 'é obrigatória quando o envio de e-mail está ativo'
               end)
  end

  def token_twilio_presente
    return if twilio_auth_token.present?

    errors.add(:twilio_auth_token,
               if twilio_auth_token_cifrado.present?
                 'gravado está ilegível (o SECRET_KEY_BASE do servidor mudou?). Cole o token novamente.'
               else
                 'é obrigatório quando o WhatsApp está ativo'
               end)
  end
end
