# app/services/analytics/planilha_entregas.rb
#
# Monta os dados da "planilha Entregas" entregue ao cliente (modelo
# "Entregas SUDESTE MM.AAAA_FINAL.xlsx"), com as 3 abas do modelo:
#
#   RESUMO      — totais/performance e quadro por coordenadoria (EMAD e UBS),
#                 com os motivos de "Não Entregue" agregados do rastreio.
#   ENTREGAS    — planilha do cliente (A..V da tabela gade_entregas_*) com as
#                 colunas de resultado W..Z preenchidas pelo rastreio (o
#                 preenchimento que hoje é feito manualmente).
#   SimpliRoute — dump cru do rastreio (todas as visitas das NFs da operação).
#
# SEGURANÇA: tabela passa pela whitelist (Operacao.sanitizar) + quote_table_name;
# colunas são whitelist fixa e as ausentes viram NULL (mesmo padrão do
# OperacaoMetricas). Bases SOMENTE LEITURA.
module Analytics
  class PlanilhaEntregas
    # Colunas da tabela gade (snake_case) -> cabeçalho exato do modelo (A..V).
    # Coluna que não existir em alguma operação sai vazia, preservando o layout.
    COLUNAS_GADE = {
      'ordem'                    => 'Ordem',
      'operacao'                 => 'Operação',
      'coordenadoria'            => 'Coordenadoria',
      'supervisao'               => 'Supervisão',
      'unidade'                  => 'Unidade',
      'data_cadastro'            => 'Data Cadastro',
      'cartao_sus'               => 'Cartão SUS',
      'nome_completo'            => 'Nome Completo',
      'endereco_completo'        => 'Endereço Completo',
      'nome_responsavel'         => 'Nome Responsável',
      'telefones'                => 'Telefones',
      'area_de_risco'            => 'Área de Risco',
      'numero_ativo'             => 'Numero Ativo',
      'num_serie_base'           => 'Num Serie Base',
      'num_serie_pulverizador'   => 'Num Serie Pulverizador',
      'observacoes'              => 'Observações',
      'endereco_sem_complemento' => 'Endereço Sem Complemento',
      'complemento'              => 'Complemento',
      'latitude'                 => 'Latitude',
      'longitude'                => 'Longitude',
      'status'                   => 'STATUS',
      'nota_fiscal'              => 'NOTA FISCAL'
    }.freeze

    # Colunas da gade cujo nome real difere da chave de COLUNAS_GADE (a chave é
    # o alias usado no SELECT e na leitura da linha). As tabelas do cliente
    # gravam a geo como `lat`/`long`, não `latitude`/`longitude` — sem isso as
    # duas colunas saíam vazias. Ordem = preferência; a 1ª que existir é usada.
    ALIAS_GADE = {
      'latitude'  => %w[latitude lat],
      'longitude' => %w[longitude long]
    }.freeze

    # Cabeçalho completo A..Z da aba ENTREGAS (gade + resultado do rastreio).
    CABECALHO = (COLUNAS_GADE.values + ['STATUS', 'ENTREGA', 'DATA OCORRÊNCIA', 'OCORRÊNCIA']).freeze

    # Aba SimpliRoute: cabeçalho exato do modelo -> colunas candidatas na tabela
    # de rastreio (a primeira que existir é usada; nenhuma existente = vazia).
    #
    # O 1º candidato de cada linha é o nome REAL conferido na
    # db_reem_simplerout_2026 (o espelho usa os nomes do export do SimpliRoute:
    # `codrivers`, `responsible_person`, `advance`, `start_of_time_window_1`,
    # `required_skills`...). Os demais candidatos são só rede de segurança para
    # o dia em que o espelho for recriado com outra grafia.
    #
    # 'Load 4' fica sem candidato de propósito: essa coluna NÃO existe no
    # espelho (só load, load_2 e load_3) — sai vazia, preservando o layout.
    RASTREIO_COLUNAS = {
      'Tracking ID'                        => %w[tracking_id],
      'Referência ID'                      => %w[reference_id],
      'Data prevista'                      => %w[planned_date],
      'Motorista'                          => %w[driver],
      'Co-pilotos'                         => %w[codrivers copilots co_pilotos copilotos],
      'Veículo'                            => %w[vehicle],
      'Título'                             => %w[title titulo],
      'Endereço'                           => %w[address],
      'ETA'                                => %w[eta],
      'ETD'                                => %w[etd],
      'Checkin'                            => %w[checkin],
      'Checkout'                           => %w[checkout],
      'Responsável'                        => %w[responsible_person receiver responsavel responsible],
      'Tempo de serviço estimado'          => %w[estimated_service_time tempo_de_servico_estimado duration],
      'Tempo de serviço realme'            => %w[real_service_time tempo_de_servico_real],
      'Antecipação'                        => %w[advance early antecipacao anticipation],
      'Atraso'                             => %w[delay atraso],
      'Latitude'                           => %w[latitude],
      'Longitude'                          => %w[longitude],
      'Checkout latitude'                  => %w[checkout_latitude],
      'Checkout longitude'                 => %w[checkout_longitude],
      'Load'                               => %w[load load_1 carga],
      'Load 2'                             => %w[load_2],
      'Load 3'                             => %w[load_3],
      'Load 4'                             => %w[],
      'Estado'                             => %w[status],
      'Comentários'                        => %w[comments checkout_comment comentarios comment],
      'Observações'                        => %w[observation observacoes],
      'Janela de horário Inicial 1'        => %w[start_of_time_window_1 window_start janela_inicial_1],
      'Janela de horário Final 1'          => %w[end_of_time_window_1 window_end janela_final_1],
      'Janela de horário Inicial 2'        => %w[start_of_time_window_2 window_start_2 janela_inicial_2],
      'Janela de horário Final 2'          => %w[end_of_time_window_2 window_end_2 janela_final_2],
      'Habilidades necessárias'            => %w[required_skills skills_required habilidades_necessarias],
      'Habilidades adicionais'             => %w[optional_skills skills_optional habilidades_adicionais],
      'Notas'                              => %w[notes notas],
      'Nome de contato'                    => %w[contact_name],
      'Telefone de contato'                => %w[contact_phone telefone_de_contato],
      'Correio eletrônico de contato'      => %w[contact_email correio_eletronico_de_contato],
      'ID da rota'                         => %w[route_id id_da_rota],
      'ID da conta'                        => %w[account_id],
      'Nome da conta'                      => %w[account_name nome_da_conta],
      'Nota Fiscal'                        => %w[nota_fiscal],
      'Termo de Recebimento'               => %w[termo_de_recebimento],
      'Foto da Fachada'                    => %w[foto_da_fachada],
      'Relatório de visita'                => %w[relatorio_de_visita visit_report],
      'Número de Série da Base (Aparelho)' => %w[numero_de_serie_da_base_aparelho num_serie_base numero_serie_base],
      # Campo do espelho que o modelo antigo do cliente ainda não tinha. Entra
      # DEPOIS das colunas do modelo para não deslocar o layout A..AT dele.
      'Protocolo de Entrega'               => %w[protocolo_de_entrega]
    }.freeze

    # Quadro do RESUMO — linhas fixas do modelo.
    COORDENADORIAS = ['CRS Sudeste', 'CRS Sul', 'CRS Leste', 'CRS Norte', 'CRS Oeste', 'CRS Centro'].freeze
    MOTIVOS        = ['Óbito', 'Responsável Ausente', 'Endereço não localizado', 'Recusa',
                      'Paciente não reside no local', 'Outros'].freeze
    GRUPOS         = %w[EMAD UBS].freeze

    MESES_PT = %w[JANEIRO FEVEREIRO MARÇO ABRIL MAIO JUNHO JULHO AGOSTO SETEMBRO OUTUBRO NOVEMBRO DEZEMBRO].freeze

    attr_reader :tabela

    def initialize(tabela)
      @tabela = Operacao.sanitizar([tabela]).first
      raise ArgumentError, "Operação inválida: #{tabela}" unless @tabela
    end

    def label
      Operacao.label(@tabela)
    end

    # ── Aba ENTREGAS ─────────────────────────────────────────────
    # Array de hashes: chaves de COLUNAS_GADE + rastreio_status/checkout/observation.
    # ORDER BY g.ctid preserva a ordem física de importação — a mesma ordem da
    # planilha original do cliente (tabelas gade são read-only após o import).
    def linhas
      @linhas ||= begin
        gade = conn.quote_table_name(@tabela)
        sql = <<~SQL
          WITH ultimo AS (
            SELECT *,
                   ROW_NUMBER() OVER (PARTITION BY reference_id ORDER BY checkout DESC NULLS LAST) AS rn
            FROM #{conn.quote_table_name(Entrega.table_name)}
            WHERE reference_id IS NOT NULL
          )
          SELECT #{selects_gade},
                 r.status      AS rastreio_status,
                 r.checkout    AS rastreio_checkout,
                 r.observation AS rastreio_observation
          FROM #{gade} g
          LEFT JOIN ultimo r ON r.rn = 1 AND r.reference_id::text = g.nota_fiscal
          ORDER BY g.ctid
        SQL
        conn.select_all(sql).to_a
      end
    end

    # ── Aba SimpliRoute ──────────────────────────────────────────
    # Todas as visitas do rastreio para as NFs da operação (inclui repetidas,
    # como no dump original do SimpliRoute).
    def linhas_rastreio
      @linhas_rastreio ||= begin
        gade = conn.quote_table_name(@tabela)
        sql = <<~SQL
          SELECT r.*
          FROM #{conn.quote_table_name(Entrega.table_name)} r
          WHERE r.reference_id::text IN (SELECT nota_fiscal FROM #{gade} WHERE nota_fiscal IS NOT NULL)
          ORDER BY r.reference_id, r.checkout NULLS LAST
        SQL
        completar_serie(conn.select_all(sql).to_a)
      end
    end

    # { cabeçalho do modelo => coluna real do rastreio (ou nil) }.
    def colunas_rastreio
      @colunas_rastreio ||= begin
        existentes = conn.columns(Entrega.table_name).map(&:name)
        RASTREIO_COLUNAS.transform_values { |cands| cands.find { |c| existentes.include?(c) } }
      end
    end

    # ── Aba RESUMO ───────────────────────────────────────────────
    # Agrega as linhas da ENTREGAS por grupo (EMAD/UBS) e coordenadoria:
    # previsão/entregue/não entregue/pendente + motivos de "Não Entregue".
    def resumo
      grupos = GRUPOS.index_with { Hash.new { |h, k| h[k] = novo_bloco } }

      linhas.each do |l|
        bloco = grupos[grupo_de(l)][coordenadoria_de(l)]
        bloco[:previsao] += 1
        status = l['rastreio_status'].to_s
        if status == 'completed'
          bloco[:entregue] += 1
        elsif Entrega::STATUS_FALHA.include?(status)
          bloco[:nao_entregue] += 1
          bloco[:motivos][motivo_de(l['rastreio_observation'])] += 1
        else
          bloco[:pendente] += 1
        end
      end

      total_prev = linhas.size
      total_real = grupos.values.sum { |coords| coords.values.sum { |b| b[:entregue] } }
      {
        mes:             "#{MESES_PT[Date.current.month - 1]}/#{Date.current.year}",
        total_previsto:  total_prev,
        total_realizado: total_real,
        performance:     (total_prev.zero? ? nil : total_real.to_f / total_prev),
        grupos:          grupos
      }
    end

    # Bloco zerado do quadro RESUMO (também usado pelo gerador xlsx).
    def novo_bloco
      { previsao: 0, entregue: 0, nao_entregue: 0, pendente: 0, motivos: Hash.new(0) }
    end

    private

    def conn
      ActiveRecord::Base.connection
    end

    # O espelho tem a coluna do número de série mas o sync NUNCA a preenche
    # (0 de ~6.000 linhas em fev/mar/mai/jul/2026) e a tabela é read-only —
    # quem busca o dado na API é o SincronizarSeriesAparelhoJob, que grava em
    # series_aparelho. Aqui só completamos as linhas em que o espelho veio
    # vazio: se um dia o sync passar a preencher, o valor dele continua valendo.
    def completar_serie(linhas)
      coluna = colunas_rastreio['Número de Série da Base (Aparelho)']
      return linhas if coluna.nil?

      pendentes = linhas.reject { |r| r[coluna].present? }
      return linhas if pendentes.empty?

      series = SerieAparelho.por_tracking(pendentes.map { |r| r['tracking_id'] })
      pendentes.each { |r| r[coluna] = series[r['tracking_id']] if series[r['tracking_id']] }
      linhas
    end

    # SELECT das colunas whitelisted; ausentes viram NULL com o mesmo alias.
    # O nome real pode diferir da chave (ver ALIAS_GADE) — o alias no SELECT é
    # sempre a chave, então quem lê a linha não precisa saber da diferença.
    def selects_gade
      existentes = conn.columns(@tabela).map(&:name)
      COLUNAS_GADE.keys.map do |c|
        real = (ALIAS_GADE[c] || [c]).find { |cand| existentes.include?(cand) }
        real ? "g.#{conn.quote_column_name(real)} AS #{c}" : "CAST(NULL AS text) AS #{c}"
      end.join(', ')
    end

    # Grupo do quadro (EMAD/UBS): coluna "operacao" da linha; sem ela, deduz do
    # nome/label da operação. Default UBS (grupo mais comum no modelo).
    def grupo_de(linha)
      texto = I18n.transliterate("#{linha['operacao']} #{@tabela} #{label}").upcase
      texto.include?('EMAD') ? 'EMAD' : 'UBS'
    end

    # Casa a coordenadoria da linha com as fixas do modelo (sem acento/caixa);
    # valores fora da lista entram como estão (viram linha extra no quadro).
    def coordenadoria_de(linha)
      bruto = linha['coordenadoria'].to_s.strip
      return COORDENADORIAS.first if bruto.empty?   # sem coluna: agrega na 1ª (Sudeste)

      chave = I18n.transliterate(bruto).upcase
      COORDENADORIAS.find { |c| I18n.transliterate(c).upcase == chave } || bruto
    end

    # Classifica a observação do insucesso nos motivos fixos do RESUMO.
    def motivo_de(observacao)
      o = I18n.transliterate(observacao.to_s).downcase
      return 'Óbito'                        if o.include?('obito')
      return 'Responsável Ausente'          if o.include?('ausente')
      return 'Endereço não localizado'      if o.include?('localizado') || o.include?('endereco')
      return 'Recusa'                       if o.include?('recus')
      return 'Paciente não reside no local' if o.include?('reside')
      'Outros'
    end
  end
end
