#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Lista, DIRETO NA API do SimpliRoute, todas as visitas de um motorista dentro
# de um período.
#
# ⚠️ POR QUE É DIA A DIA (e não uma chamada só):
# A API NÃO tem filtro de intervalo. Testado contra a API real em 21/07/2026
# (bin/sondar_busca_nf): `planned_date_from/to`, `planned_date_after/before`,
# `__gte/__lte`, `since/until`, `start_date/end_date`, `date_from/to` — TODOS
# ignorados em silêncio (200 + o conjunto padrão inteiro). E SEM `planned_date`
# a API devolve um conjunto padrão (~2469 visitas) que NÃO cobre o histórico.
# Logo: `planned_date` é obrigatório e o período vira N requisições, uma por dia,
# disparadas em paralelo.
#
# ⚠️ Não filtramos motorista no servidor: `search=` só foi comprovado para NF, e
# parâmetro que a API não registra é IGNORADO em silêncio (devolve o dia inteiro
# com HTTP 200). Então baixamos o dia inteiro e filtramos AQUI — é lento
# (~3,8 MB / ~9 s por dia) mas é a única forma correta.
#
# SÓ LEITURA: o script faz apenas GET. Não altera nada.
#
# Rode NO SERVIDOR (é lá que vive o SIMPLIROUTE_TOKEN):
#
#   SIMPLIROUTE_TOKEN=xxx bin/entregas_do_motorista \
#     --motorista "JOSE DA SILVA" --de 2026-08-01 --ate 2026-08-14
#
#   ... --csv /tmp/entregas.csv    grava o resultado em CSV
#   ... --listar-motoristas        só mostra quem aparece no período (sem filtrar)
#   ... --campos                   mostra as chaves cruas de uma visita (diagnóstico)
#
# Alternativa MUITO mais barata quando não precisa do dado ao vivo: o espelho
# local (model Entrega, tabela db_reem_simplerout_2026) já tem a coluna `driver`
# e responde isso numa query só:
#
#   Entrega.no_periodo('2026-08-01', '2026-08-14').do_motorista('JOSE DA SILVA')
#
# O espelho sincroniza depois da API, então só use este script quando precisar
# do estado ATUAL ou desconfiar da sync.

require 'net/http'
require 'json'
require 'uri'
require 'date'
require 'csv'

BASE  = ENV.fetch('SIMPLIROUTE_BASE_URL', 'https://api.simpliroute.com')
TOKEN = ENV['SIMPLIROUTE_TOKEN'].to_s
THREADS = 6 # mesmo lote usado no sondar_busca_nf; acima disso a API estrangula

def sair(msg)
  warn msg
  exit 1
end

sair('Defina SIMPLIROUTE_TOKEN no ambiente.') if TOKEN.empty?

motorista = nil
de = nil
ate = nil
csv_path = nil
listar = false
campos = false
ARGV.each_with_index do |a, i|
  case a
  when '--motorista'          then motorista = ARGV[i + 1]
  when '--de'                 then de        = ARGV[i + 1]
  when '--ate', '--até'       then ate       = ARGV[i + 1]
  when '--csv'                then csv_path  = ARGV[i + 1]
  when '--listar-motoristas'  then listar    = true
  when '--campos'             then campos    = true
  end
end

sair('Uso: bin/entregas_do_motorista --motorista "NOME" --de YYYY-MM-DD --ate YYYY-MM-DD') if de.to_s.empty? || ate.to_s.empty?
sair('Informe --motorista, ou use --listar-motoristas.') if motorista.to_s.empty? && !listar && !campos

inicio = begin
  Date.parse(de)
rescue ArgumentError
  sair("Data inválida em --de: #{de}")
end
fim = begin
  Date.parse(ate)
rescue ArgumentError
  sair("Data inválida em --ate: #{ate}")
end
sair('--de é depois de --ate.') if inicio > fim

DIAS = (inicio..fim).to_a

# Normaliza nome para comparar: sem acento, sem espaço duplo, maiúsculo.
# O nome no SimpliRoute é digitado por gente — "José  da Silva" e "JOSE DA SILVA"
# são o mesmo motorista.
def normalizar(nome)
  nome.to_s
      .unicode_normalize(:nfd).gsub(/\p{Mn}/, '')
      .upcase.gsub(/\s+/, ' ').strip
end

def requisicao(caminho)
  uri = URI.join(BASE, caminho)
  req = Net::HTTP::Get.new(uri)
  req['Authorization'] = "Token #{TOKEN}"
  req['Accept']        = 'application/json'

  res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
                                            open_timeout: 10, read_timeout: 180) { |h| h.request(req) }
  return [res.code.to_i, nil] unless res.code.to_i == 200

  [200, (res.body.to_s.empty? ? nil : JSON.parse(res.body))]
rescue StandardError => e
  [0, "ERRO: #{e.class}: #{e.message}"]
end

# A resposta pode ser lista crua OU paginada ({count, next, results}).
def itens(corpo)
  return corpo                   if corpo.is_a?(Array)
  return Array(corpo['results']) if corpo.is_a?(Hash) && corpo.key?('results')

  []
end

# Onde está o nome do motorista numa visita. A lista-por-data às vezes traz o
# campo achatado ('driver'), às vezes só o id, às vezes um hash aninhado — por
# isso procuramos em várias formas em vez de fixar uma. Se nada casar, o script
# avisa e o --campos mostra o formato real.
CHAVES_MOTORISTA = %w[driver driver_name route_driver driver_full_name].freeze

def nome_do_motorista(visita)
  CHAVES_MOTORISTA.each do |k|
    v = visita[k]
    next if v.nil? || v == '' || v.is_a?(Numeric)

    return v                                if v.is_a?(String)
    return (v['name'] || v['full_name'] ||
            [v['first_name'], v['last_name']].compact.join(' ')) if v.is_a?(Hash)
  end
  nil
end

puts "Base: #{BASE}"
puts "Período: #{inicio.strftime('%d/%m/%Y')} a #{fim.strftime('%d/%m/%Y')}  (#{DIAS.size} dia(s), #{THREADS} em paralelo)"
puts "Motorista: #{motorista}" if motorista
puts '(a API não filtra por intervalo nem por motorista — baixando dia a dia; pode levar ~1 min)'
puts

# ── Varredura dia a dia, em lotes ────────────────────────────────────────────
t0 = Time.now
visitas = []
falhas  = []
mutex   = Mutex.new

DIAS.each_slice(THREADS) do |lote|
  lote.map do |dia|
    Thread.new do
      cod, corpo = requisicao("/v1/routes/visits/?planned_date=#{dia.iso8601}")
      lista = cod == 200 ? itens(corpo) : []
      mutex.synchronize do
        if cod == 200
          # planned_date às vezes não volta na visita — garantimos o dia pedido.
          lista.each { |v| v['_dia'] = dia.iso8601 }
          visitas.concat(lista)
          print '.'
        else
          falhas << [dia, cod, corpo]
          print 'x'
        end
      end
    end
  end.each(&:join)
end
puts "\nBaixadas #{visitas.size} visita(s) em #{(Time.now - t0).round(1)}s"
falhas.each { |d, c, e| warn "  ⚠️  #{d}: HTTP #{c} #{e.inspect[0, 120]}" }
sair('Nenhuma visita voltou — período sem operação ou token sem acesso.') if visitas.empty?

# ── Diagnóstico do formato ───────────────────────────────────────────────────
if campos
  puts "\nChaves de uma visita crua:"
  puts visitas.first.keys.sort.inspect
  puts "\nExemplo (recortado):"
  puts JSON.pretty_generate(visitas.first)[0, 2000]
  exit 0
end

com_nome = visitas.count { |v| nome_do_motorista(v) }
if com_nome.zero?
  warn "\n⚠️  NENHUMA visita trouxe nome de motorista nas chaves #{CHAVES_MOTORISTA.inspect}."
  warn '   A lista-por-data pode estar devolvendo só o id da rota. Rode com --campos'
  warn '   para ver o formato real, ou use o espelho local (Entrega#driver).'
  exit 1
end
warn "\n⚠️  #{visitas.size - com_nome} visita(s) sem motorista preenchido (ficam de fora do filtro)." if com_nome < visitas.size

# ── Só listar quem apareceu ──────────────────────────────────────────────────
if listar
  puts "\nMotoristas no período:"
  visitas.group_by { |v| nome_do_motorista(v) }
         .reject { |k, _| k.nil? }
         .sort_by { |_, vs| -vs.size }
         .each { |nome, vs| puts "  #{vs.size.to_s.rjust(5)}  #{nome}" }
  exit 0
end

# ── Filtro pelo motorista ────────────────────────────────────────────────────
alvo = normalizar(motorista)
dele = visitas.select { |v| normalizar(nome_do_motorista(v)) == alvo }

if dele.empty?
  puts "\nNenhuma visita de #{motorista.inspect} no período."
  parecidos = visitas.map { |v| nome_do_motorista(v) }.compact.uniq
                     .select { |n| normalizar(n).include?(alvo) || alvo.include?(normalizar(n)) }
  puts "Nomes parecidos que existem: #{parecidos.inspect}" if parecidos.any?
  puts 'Use --listar-motoristas para ver a grafia exata.'
  exit 0
end

dele.sort_by! { |v| [v['_dia'].to_s, v['checkout_time'].to_s] }

puts "\n#{'─' * 100}"
puts "#{dele.size} visita(s) de #{motorista}"
puts '─' * 100
printf("%-11s %-10s %-9s %-19s %-40s\n", 'DATA', 'NF', 'STATUS', 'CHECKOUT', 'DESTINO')
dele.each do |v|
  printf("%-11s %-10s %-9s %-19s %-40s\n",
         Date.parse(v['_dia']).strftime('%d/%m/%Y'),
         v['reference'].to_s[0, 10],
         v['status'].to_s[0, 9],
         v['checkout_time'].to_s[0, 19].tr('T', ' '),
         v['title'].to_s[0, 40])
end

puts '─' * 100
por_status = dele.group_by { |v| v['status'].to_s }.transform_values(&:size)
puts "Total: #{dele.size}   " + por_status.sort_by { |_, n| -n }.map { |s, n| "#{s}=#{n}" }.join('  ')
atendidas = dele.count { |v| %w[completed failed].include?(v['status'].to_s) && v['checkout_time'].to_s != '' }
puts "Atendidas (foi ao local, com checkout): #{atendidas}"
puts "Dias com entrega: #{dele.map { |v| v['_dia'] }.uniq.size}"

# ── CSV ──────────────────────────────────────────────────────────────────────
if csv_path
  CSV.open(csv_path, 'w') do |csv|
    csv << ['Data', 'NF', 'Tracking', 'ID visita', 'Motorista', 'Status',
            'Checkout', 'Título', 'Endereço', 'Motivo (UUID)', 'Comentário']
    dele.each do |v|
      csv << [Date.parse(v['_dia']).strftime('%d/%m/%Y'), v['reference'], v['tracking_id'], v['id'],
              nome_do_motorista(v), v['status'], v['checkout_time'], v['title'], v['address'],
              v['checkout_observation'], v['checkout_comment']]
    end
  end
  puts "\nCSV gravado em #{csv_path}"
end
