# app/models/operacao.rb
#
# Operações são as tabelas EXTERNAS que o cliente carrega no banco, uma por
# operação/mês (ex.: "gade_entregas_ubs_sul_leste_jun_2026"). Cada tabela tem a
# coluna `nota_fiscal`, que casa com db_reem_simplerout_2026.reference_id::text.
#
# Estas tabelas são SOMENTE LEITURA — apenas SELECT, nunca migration/INSERT/etc.
#
# Como o nome da tabela entra em SQL (FROM dinâmico), TODA entrada externa passa
# por uma whitelist (#nomes_validos) antes de ser usada — proteção anti-injection.
class Operacao
  PREFIXO = ENV.fetch('OPERACOES_TABLE_PREFIX', 'gade_entregas_').freeze

  class << self
    # [{ tabela: 'gade_entregas_...', label: 'Ubs Sul Leste Jun 2026' }, ...]
    def todas
      nomes_validos.map { |t| { tabela: t, label: label(t) } }
    end

    # Nomes reais de tabelas no schema public que batem com o prefixo.
    # Sem cache de processo: o cliente sobe novas tabelas de operação a qualquer
    # momento e elas precisam aparecer sem reiniciar o servidor (a consulta ao
    # catálogo é barata).
    def nomes_validos
      conn.select_values(
        "SELECT table_name FROM information_schema.tables " \
        "WHERE table_schema = 'public' AND table_name LIKE #{conn.quote("#{PREFIXO}%")} ORDER BY table_name"
      )
    end

    # Interseção da lista recebida com as tabelas que existem de fato.
    def sanitizar(lista)
      Array(lista).map(&:to_s).uniq & nomes_validos
    end

    def valida?(nome)
      nomes_validos.include?(nome.to_s)
    end

    # Rótulo amigável: tira o prefixo e formata em CAIXA ALTA (para não parecer o
    # nome cru da tabela do banco).
    def label(tabela)
      tabela.to_s.delete_prefix(PREFIXO).tr('_', ' ').strip.upcase
    end

    # Meses usados nos nomes das tabelas (gade_entregas_emad_fev_2026). Aceita a
    # abreviação (fev) E o nome por extenso (fevereiro) — o cliente usa os dois
    # (ex.: "..._ubs_norte_maio_2026"), então ambos precisam cair no mês certo.
    MESES = {
      'jan' => [1, 'Janeiro'],   'janeiro'   => [1, 'Janeiro'],
      'fev' => [2, 'Fevereiro'], 'fevereiro' => [2, 'Fevereiro'],
      'mar' => [3, 'Março'],     'marco'     => [3, 'Março'], 'março' => [3, 'Março'],
      'abr' => [4, 'Abril'],     'abril'     => [4, 'Abril'],
      'mai' => [5, 'Maio'],      'maio'      => [5, 'Maio'],
      'jun' => [6, 'Junho'],     'junho'     => [6, 'Junho'],
      'jul' => [7, 'Julho'],     'julho'     => [7, 'Julho'],
      'ago' => [8, 'Agosto'],    'agosto'    => [8, 'Agosto'],
      'set' => [9, 'Setembro'],  'setembro'  => [9, 'Setembro'],
      'out' => [10, 'Outubro'],  'outubro'   => [10, 'Outubro'],
      'nov' => [11, 'Novembro'], 'novembro'  => [11, 'Novembro'],
      'dez' => [12, 'Dezembro'], 'dezembro'  => [12, 'Dezembro']
    }.freeze

    # Agrupa as operações por mês/ano (extraído do nome da tabela), do mais recente
    # para o mais antigo; as que não têm mês reconhecível caem em "Outras".
    # Retorna um Array de [titulo_do_grupo, [{ tabela:, label: }, ...]].
    def agrupadas_por_mes
      grupos = Hash.new { |h, k| h[k] = [] }
      ordem  = {}

      todas.each do |op|
        g = grupo_de(op[:tabela])
        grupos[g[:titulo]] << { tabela: op[:tabela], label: g[:item] }
        ordem[g[:titulo]] ||= g[:ordem]
      end

      grupos.each_value { |itens| itens.sort_by! { |i| i[:label] } }
      grupos.sort_by { |titulo, _| ordem[titulo] }.map { |titulo, itens| [titulo, itens] }
    end

    # Em QUAIS operações cada NF aparece: { '85382' => ['UBS NORTE AGO 2026'] }.
    # NFs que não estiverem em nenhuma planilha simplesmente não vêm no Hash — é
    # assim que a tela de pendentes marca "fora da operação" (plano avulso/de
    # inclusão) sem precisar de uma query por tabela.
    #
    # SEGURANÇA: as tabelas vêm de #nomes_validos (catálogo) + quote_table_name;
    # as NFs entram por conn.quote. Uma query só, com UNION ALL.
    def por_notas(notas)
      lista = Array(notas).map(&:to_s).map(&:strip).reject(&:empty?).uniq
      return {} if lista.empty?

      tabelas = nomes_validos
      return {} if tabelas.empty?

      valores = lista.map { |n| conn.quote(n) }.join(', ')
      sql = tabelas.map do |t|
        "SELECT nota_fiscal, #{conn.quote(label(t))} AS operacao " \
        "FROM #{conn.quote_table_name(t)} WHERE nota_fiscal IN (#{valores})"
      end.join(' UNION ALL ')

      conn.select_all(sql).to_a
          .group_by { |r| r['nota_fiscal'].to_s }
          .transform_values { |rows| rows.map { |r| r['operacao'] }.uniq.sort }
    end

    # Dados agregados das entregas das operações informadas, para pré-preencher o
    # formulário de nova consolidação. Une as NFs de todas as tabelas válidas.
    def dados(tabelas)
      validas = sanitizar(tabelas)
      return vazio if validas.empty?

      entregas = Entrega.da_conta_gade.da_operacoes(validas)
      pagas    = entregas.pagas
      {
        motoristas: entregas.distinct.order(:driver).pluck(:driver).compact.reject(&:blank?),
        veiculos:   entregas.distinct.order(:vehicle).pluck(:vehicle).compact.reject(&:blank?),
        # Período sugerido pela DATA REAL (checkout), igual ao filtro de
        # elegibilidade; cai para planned_date se ainda não houver pagas.
        inicio:     (pagas.minimum(:checkout) || entregas.minimum(:planned_date))&.to_date,
        fim:        (pagas.maximum(:checkout) || entregas.maximum(:planned_date))&.to_date
      }
    end

    # Número do mês a partir de um token do nome ("ago", "agosto", "AGOSTO").
    # Público de propósito: Romaneios::CasadorDeOperacao precisa da MESMA tabela de
    # meses para casar o nome do plano do SimpliRoute com a tabela da operação, e
    # duplicar esse mapa seria criar uma segunda verdade que sai de sincronia.
    def mes_numero(token)
      MESES[token.to_s.downcase]&.first
    end

    private

    # Extrai { titulo:, item:, ordem: } do nome da tabela.
    # Ex.: gade_entregas_emad_fev_2026 → titulo "Fevereiro 2026", item "Emad".
    def grupo_de(tabela)
      tokens = tabela.to_s.delete_prefix(PREFIXO).split('_')
      idx    = tokens.index { |t| MESES.key?(t.downcase) }

      if idx
        num, nome = MESES[tokens[idx].downcase]
        ano       = tokens[idx + 1] =~ /\A\d{4}\z/ ? tokens[idx + 1].to_i : nil
        resto     = (tokens[0...idx] + tokens[(ano ? idx + 2 : idx + 1)..]).join(' ').strip
        {
          titulo: ano ? "#{nome} #{ano}" : nome,
          item:   (resto.presence || nome).to_s.upcase,
          # mais recente primeiro; sem ano vai pro fim do bloco do mês
          ordem:  [-(ano || 0), -num]
        }
      else
        { titulo: 'Outras', item: label(tabela), ordem: [1, 0] }
      end
    end

    def vazio
      { motoristas: [], veiculos: [], inicio: nil, fim: nil }
    end

    def conn
      ActiveRecord::Base.connection
    end
  end
end
