require 'rails_helper'

RSpec.describe Analytics::OperacaoMetricas do
  # Linhas simuladas (mesma forma do retorno de select_all) — evita depender das
  # tabelas externas no teste. Stubamos #registros para focar nas agregações.
  # Uma NF por linha: nesse conjunto nenhuma visita se repete, então as duas
  # camadas (visitas x notas) coincidem.
  let(:rows) do
    [
      row(nf: 1, status: 'completed', driver: 'Carlos', sts: 'STS PERUS',     gade: 'RECORRENTE', checkout: '2026-06-22 10:00:00', clat: '-23.5', clng: '-46.6'),
      row(nf: 2, status: 'completed', driver: 'Carlos', sts: 'STS PERUS',     gade: 'RECORRENTE', checkout: '2026-06-22 11:00:00', lat: '-23.6', lng: '-46.7'),
      row(nf: 3, status: 'completed', driver: 'Pedro',  sts: 'STS PIRITUBA',  gade: 'RECORRENTE', checkout: '2026-06-23 09:00:00', clat: '-23.4', clng: '-46.5'),
      row(nf: 4, status: 'failed',    driver: 'Pedro',  sts: 'STS PIRITUBA',  gade: 'NOVO', obs: 'ÓBITO', checkout: '2026-06-23 12:00:00'),
      row(nf: 5, status: 'failed',    driver: 'Carlos', sts: 'STS PERUS',     gade: 'NOVO', obs: 'ÓBITO', checkout: '2026-06-22 13:00:00'),
      row(nf: 6, status: 'pending',   driver: 'Marcos', sts: 'STS PERUS',     gade: 'NOVO', planned: '2026-06-24')
    ]
  end

  subject(:metricas) do
    described_class.new(tabelas: [], inicio: Date.new(2026, 6, 1), fim: Date.new(2026, 6, 30))
  end

  before do
    # Isola das tabelas externas: não toca o catálogo nem db_reem_simplerout_2026.
    allow(Operacao).to receive(:sanitizar).and_return([])
    allow(metricas).to receive(:registros).and_return(rows)
  end

  it 'conta KPIs (total/sucesso/recusas/pendentes)' do
    expect(metricas.total).to eq(6)
    expect(metricas.sucesso).to eq(3)
    expect(metricas.recusas).to eq(2)
    expect(metricas.pendentes).to eq(1)
  end

  it 'sem NF repetida, visitas e notas coincidem' do
    expect(metricas.total_visitas).to eq(6)
    expect(metricas.retentativas).to eq(0)
    expect(metricas.notas_reentregues).to eq(0)
  end

  # A regressão que motivou as duas camadas: a mesma NF visitada duas vezes
  # (falhou, depois entregou) contava 2 no dashboard financeiro e 1 aqui, sem
  # nada na tela explicando a diferença — e o insucesso desaparecia por completo.
  context 'com NF visitada mais de uma vez' do
    let(:rows) do
      [
        row(nf: 10, status: 'failed',    driver: 'Carlos', sts: 'STS PERUS', gade: 'NOVO', obs: 'AUSENTE', checkout: '2026-06-10 09:00:00'),
        row(nf: 10, status: 'completed', driver: 'Pedro',  sts: 'STS PERUS', gade: 'NOVO', checkout: '2026-06-12 09:00:00'),
        row(nf: 11, status: 'completed', driver: 'Pedro',  sts: 'STS PERUS', gade: 'NOVO', checkout: '2026-06-12 10:00:00')
      ]
    end

    it 'conta a NF uma vez, pelo status da ÚLTIMA visita' do
      expect(metricas.total).to eq(2)
      expect(metricas.sucesso).to eq(2)
      expect(metricas.recusas).to eq(0)
    end

    it 'expõe as idas ao local em vez de descartá-las em silêncio' do
      expect(metricas.total_visitas).to eq(3)
      expect(metricas.retentativas).to eq(1)
      expect(metricas.visitas_insucesso).to eq(1)
      expect(metricas.notas_reentregues).to eq(1)
    end

    it 'visita sem checkout perde para a que tem' do
      sem_checkout = [
        row(nf: 20, status: 'pending',   driver: 'Marcos', sts: 'STS PERUS', gade: 'NOVO', planned: '2026-06-24'),
        row(nf: 20, status: 'completed', driver: 'Marcos', sts: 'STS PERUS', gade: 'NOVO', checkout: '2026-06-20 09:00:00')
      ]
      m = described_class.new(tabelas: [])
      allow(m).to receive(:registros).and_return(sem_checkout)

      expect(m.total).to eq(1)
      expect(m.sucesso).to eq(1)
      expect(m.pendentes).to eq(0)
    end
  end

  it 'calcula insucessos %' do
    ins = metricas.insucessos_pct
    expect(ins[:completed]).to eq(3)
    expect(ins[:failed]).to eq(2)
    expect(ins[:pct_completed]).to eq(50.0)
    expect(ins[:pct_failed]).to eq(33.33)
  end

  it 'agrupa índices de falha por observação' do
    expect(metricas.indices_falha).to eq([{ observation: 'ÓBITO', total: 2, pct: 100.0 }])
  end

  it 'agrupa por status_gade' do
    expect(metricas.por_status_gade).to contain_exactly(
      { status: 'RECORRENTE', total: 3 },
      { status: 'NOVO', total: 3 }
    )
  end

  it 'conta NFs por motorista (todas), desc' do
    expect(metricas.por_motorista.first).to eq({ nome: 'Carlos', total: 3 })
    expect(metricas.por_motorista.map { |h| h[:nome] }).to eq(%w[Carlos Pedro Marcos])
  end

  it 'conta entregas concluídas por STS (contact_name)' do
    expect(metricas.por_sts).to eq([
      { nome: 'STS PERUS', total: 2 },
      { nome: 'STS PIRITUBA', total: 1 }
    ])
  end

  it 'agrupa por dia (completas x falhas)' do
    pd = metricas.por_dia
    expect(pd[:labels]).to eq(['22/06/2026', '23/06/2026'])
    expect(pd[:completed]).to eq([2, 1])
    expect(pd[:failed]).to eq([1, 1])
  end

  it 'monta pontos do mapa (prefere checkout, cai p/ checkin)' do
    expect(metricas.pontos_mapa).to eq([[-23.5, -46.6], [-23.6, -46.7], [-23.4, -46.5]])
  end

  context 'com cross-filter (filtros)' do
    subject(:filtrada) { described_class.new(tabelas: [], filtros: { 'driver' => 'Carlos' }) }

    before { allow(filtrada).to receive(:registros).and_return(rows) }

    it 'restringe todas as agregações ao valor filtrado' do
      expect(filtrada.total).to eq(3)
      expect(filtrada.sucesso).to eq(2)
      expect(filtrada.recusas).to eq(1)
      expect(filtrada.por_motorista).to eq([{ nome: 'Carlos', total: 3 }])
    end

    it 'ignora filtro com valor em branco' do
      vazio = described_class.new(tabelas: [], filtros: { 'driver' => '' })
      allow(vazio).to receive(:registros).and_return(rows)
      expect(vazio.total).to eq(6)
    end
  end

  context 'sem registros' do
    before { allow(metricas).to receive(:registros).and_return([]) }

    it 'não divide por zero' do
      expect(metricas.total).to eq(0)
      expect(metricas.insucessos_pct[:pct_failed]).to eq(0.0)
      expect(metricas.indices_falha).to eq([])
      expect(metricas.pontos_mapa).to eq([])
      expect(metricas.centro_mapa).to eq(described_class::SP_CENTRO)
    end
  end

  def row(status:, driver:, sts:, gade:, nf: nil, obs: nil, checkout: nil, planned: nil, lat: nil, lng: nil, clat: nil, clng: nil)
    {
      'reference_id' => nf, 'status' => status, 'driver' => driver, 'contact_name' => sts, 'status_gade' => gade,
      'observation' => obs, 'checkout' => checkout, 'planned_date' => planned,
      'latitude' => lat, 'longitude' => lng,
      'checkout_latitude' => clat, 'checkout_longitude' => clng
    }
  end
end
