#!/usr/bin/env ruby
# frozen_string_literal: true
#
# Descobre se a API do SimpliRoute aceita buscar visita por NF e/ou por período
# — a doc oficial só documenta `?planned_date=` de UM dia.
#
# ── RESULTADO DA RODADA DE 21/07/2026 (contra a API real) ───────────────────
#   ✔ `&search=<NF>` FUNCIONA (não documentado): dia inteiro cai de ~3,8 MB/9 s
#     para ~1 KB/0,6 s. É o que a tela usa hoje.
#   ✘ `reference`, `reference_id`, `q`, `title`, `reference__*` — IGNORADOS em
#     silêncio (200 + dia inteiro).
#   ✘ Intervalo (`planned_date_from/to`, `since/until`, `__gte/__lte`,
#     `date_from/to`) — TODOS ignorados: devolveram 2469, igual ao controle sem
#     parâmetro nenhum.
#   ✘ Sem `planned_date` a API devolve um conjunto padrão (~2469) que NÃO cobre
#     o histórico — `?search=<NF>` sozinho escondeu a visita mais antiga da NF.
#   ⇒ `planned_date` continua obrigatório; a busca por NF é dia a dia + search.
#
# Rode de novo se desconfiar que a API mudou.
#
# SÓ LEITURA: o script faz apenas GET/OPTIONS. Não altera nada.
#
# Rode NO SERVIDOR (é lá que vive o SIMPLIROUTE_TOKEN):
#
#   SIMPLIROUTE_TOKEN=xxx bin/sondar_busca_nf --nf 82891 --data 2026-07-17
#   SIMPLIROUTE_TOKEN=xxx bin/sondar_busca_nf --nf 82891 --data 2026-07-17 --sem-data
#
# --data deve ser um dia em que a NF EXISTE (é a referência de comparação).
# --sem-data testa a consulta sem `planned_date` nenhum; pode ser lenta ou vir
# gigante, por isso fica de fora por padrão.
#
# ⚠️ Por que comparar contagens: a API é Django REST. Parâmetro que o backend
# não registra é IGNORADO em silêncio — devolve 200 e a lista inteira. Então
# "voltou 200 com resultados" NÃO prova que filtrou. A prova é o filtro DIMINUIR
# o resultado do dia e sobrar só a NF pedida.

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

BASE  = ENV.fetch('SIMPLIROUTE_BASE_URL', 'https://api.simpliroute.com')
TOKEN = ENV['SIMPLIROUTE_TOKEN'].to_s

def sair(msg)
  warn msg
  exit 1
end

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

nf = nil
data = nil
sem_data = false
ARGV.each_with_index do |a, i|
  nf       = ARGV[i + 1] if a == '--nf'
  data     = ARGV[i + 1] if a == '--data'
  sem_data = true        if a == '--sem-data'
end
sair('Uso: bin/sondar_busca_nf --nf <numero> --data <YYYY-MM-DD> [--sem-data]') if nf.to_s.empty? || data.to_s.empty?

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

  t0 = Time.now
  res = Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https',
                                            open_timeout: 10, read_timeout: 120) { |h| h.request(req) }
  corpo = begin
    res.body.to_s.empty? ? nil : JSON.parse(res.body)
  rescue JSON::ParserError
    res.body.to_s[0, 200]
  end
  [res.code.to_i, corpo, res.body.to_s.bytesize, Time.now - t0]
rescue StandardError => e
  [0, "ERRO: #{e.class}: #{e.message}", 0, Time.now - t0]
end

# A resposta pode ser lista crua OU paginada ({count, next, results}). No caso
# paginado o `count` já é o total — é a informação mais barata que existe aqui.
def itens(corpo)
  return corpo                      if corpo.is_a?(Array)
  return Array(corpo['results'])    if corpo.is_a?(Hash) && corpo.key?('results')

  []
end

def total(corpo, lista)
  corpo.is_a?(Hash) && corpo['count'] ? corpo['count'] : lista.size
end

def titulo(t)
  puts "\n#{'─' * 70}\n#{t}\n#{'─' * 70}"
end

puts "Base: #{BASE}   NF: #{nf}   Data de referência: #{data}"

# ── 1. Linha de base: o dia inteiro, do jeito que a tela faz hoje ────────────
titulo("1. LINHA DE BASE  ·  GET /v1/routes/visits/?planned_date=#{data}")
cod, corpo, bytes, seg = requisicao("/v1/routes/visits/?planned_date=#{data}")
sair("Falhou (HTTP #{cod}): #{corpo.inspect}") unless cod == 200

base_lista = itens(corpo)
base_total = total(corpo, base_lista)
base_ids   = base_lista.map { |v| v['id'] }
da_nf      = base_lista.select { |v| v['reference'].to_s == nf.to_s }

puts "  HTTP 200 em #{seg.round(2)}s — #{bytes} bytes"
puts "  visitas no dia .......... #{base_total}"
puts "  paginado? ............... #{corpo.is_a?(Hash) ? "sim (count=#{corpo['count']}, next=#{corpo['next'].inspect})" : 'não (lista crua)'}"
puts "  visitas com a NF #{nf} ... #{da_nf.size}"
da_nf.each { |v| puts "     id=#{v['id']}  tracking=#{v['tracking_id'].inspect}  status=#{v['status']}  data=#{v['planned_date']}" }
puts "  ⚠️  tracking_id vem NULO na lista-por-data" if da_nf.any? && da_nf.all? { |v| v['tracking_id'].nil? }
sair("A NF #{nf} não aparece em #{data}. Passe uma data em que ela exista — sem isso não há como comparar.") if da_nf.empty?

# ── 2. Filtro por NF: o parâmetro DIMINUI o resultado do dia? ────────────────
# Se a contagem não mudar, o parâmetro foi ignorado (mesmo com HTTP 200).
titulo('2. FILTRO POR NF  ·  <dia> + <param>=NF   (diminuiu = filtro real)')
%w[reference reference_id search q title reference__exact reference__icontains].each do |param|
  cod, corpo, _b, seg = requisicao("/v1/routes/visits/?planned_date=#{data}&#{param}=#{nf}")
  lista = itens(corpo)
  qtd   = total(corpo, lista)

  veredito =
    if cod != 200                                       then "HTTP #{cod} — não aceito"
    elsif qtd == base_total                             then 'IGNORADO (mesma contagem do dia inteiro)'
    elsif qtd.zero?                                     then 'aceito porém vazio — filtrou demais'
    elsif lista.all? { |v| v['reference'].to_s == nf.to_s } then "★ FILTRA DE VERDADE — #{qtd} visita(s), todas da NF"
    else                                                     "reduziu p/ #{qtd} mas veio NF de fora"
    end
  puts "  #{param.ljust(22)} #{veredito}  (#{seg.round(2)}s)"
end

# ── 3. Filtro por período ────────────────────────────────────────────────────
# Comparação: o intervalo <data>..<data+1> tem que devolver MAIS que só <data>.
dia2 = (Date.parse(data) + 1).to_s
titulo("3. FILTRO POR PERÍODO  ·  #{data} a #{dia2}   (> #{base_total} = intervalo respeitado)")
[
  %w[planned_date_from planned_date_to],
  %w[planned_date_after planned_date_before],
  %w[planned_date__gte planned_date__lte],
  %w[since until],
  %w[start_date end_date],
  %w[date_from date_to]
].each do |de, ate|
  cod, corpo, _b, seg = requisicao("/v1/routes/visits/?#{de}=#{data}&#{ate}=#{dia2}")
  lista = itens(corpo)
  qtd   = total(corpo, lista)

  veredito =
    if cod != 200        then "HTTP #{cod} — não aceito"
    elsif qtd > base_total then "★ RESPEITA O INTERVALO — #{qtd} visitas (dia sozinho: #{base_total})"
    elsif qtd == base_total then "mesma contagem do dia — inconclusivo/ignorado"
    else                      "#{qtd} visitas — menos que o dia sozinho, estranho"
    end
  puts "  #{"#{de}/#{ate}".ljust(38)} #{veredito}  (#{seg.round(2)}s)"
end

# ── 4. OPTIONS: o DRF às vezes lista os filtros aceitos ──────────────────────
titulo('4. OPTIONS /v1/routes/visits/   (metadados do endpoint)')
cod, corpo, = requisicao('/v1/routes/visits/', metodo: :options)
if cod == 200 && corpo.is_a?(Hash)
  puts "  métodos ... #{corpo['renders'] ? corpo.slice('name', 'description', 'parses').inspect[0, 300] : corpo.keys.inspect}"
  filtros = corpo['filters'] || corpo['filter_fields'] || corpo.dig('actions', 'GET')
  puts "  filtros ... #{filtros ? filtros.inspect[0, 500] : '(não declarados)'}"
else
  puts "  HTTP #{cod} — #{corpo.inspect[0, 200]}"
end

# ── 5. Sem planned_date nenhum — a pergunta direta ───────────────────────────
unless sem_data
  puts "\n(Pulei o teste SEM planned_date. Rode com --sem-data para incluir — pode demorar ou vir gigante.)"
  exit 0
end

titulo('5. SEM planned_date  ·  a API devolve a NF inteira de uma vez?')
[
  "/v1/routes/visits/?reference=#{nf}",
  "/v1/routes/visits/?search=#{nf}",
  '/v1/routes/visits/'
].each do |caminho|
  cod, corpo, bytes, seg = requisicao(caminho)
  lista = itens(corpo)
  qtd   = total(corpo, lista)
  casam = lista.count { |v| v['reference'].to_s == nf.to_s }

  puts "  GET #{caminho}"
  if cod == 200
    puts "     #{qtd} visita(s), #{casam} com a NF #{nf} — #{bytes} bytes em #{seg.round(2)}s"
    datas = lista.select { |v| v['reference'].to_s == nf.to_s }.map { |v| v['planned_date'] }.uniq
    puts "     datas da NF: #{datas.inspect}" if datas.any?
    puts '     ★ RESOLVE TUDO: a NF veio inteira, sem varrer dia a dia' if casam > 1 && casam == qtd
  else
    puts "     HTTP #{cod} — #{corpo.inspect[0, 200]}"
  end
end
