# app/controllers/admin/edicao_lancamentos_controller.rb
#
# Correção de lançamentos do SimpliRoute pelo ADM. Busca a entrega pela NF (na
# base de rastreio local), resolve o id numérico na API e grava a alteração
# DIRETO na API do SimpliRoute — registrando tudo no AuditoriaLog.
#
# ⚠️ A base de rastreio local (Entrega) é só leitura e sincroniza depois: a
# alteração só reflete no painel na próxima sincronização.
class Admin::EdicaoLancamentosController < ApplicationController
  before_action :garantir_configurado

  # Campos que o ADM pode editar (fase 1). checkout_observation = UUID do motivo.
  CAMPOS_EDITAVEIS = %w[
    status checkout_observation checkout_comment notes
    checkout_time checkout_latitude checkout_longitude
  ].freeze

  STATUS_VALIDOS = %w[pending partial completed failed canceled].freeze

  def show
    authorize :edicao_lancamento
    @motivos = motivos_seguros
  end

  # GET /admin/edicao_lancamento/buscar?nf=79774 → JSON com o estado atual.
  def buscar
    authorize :edicao_lancamento

    nf = params[:nf].to_s.strip
    return render_erro('Informe o número da NF.') if nf.blank?

    entrega = Entrega.por_nf(nf).first
    return render_erro("NF #{nf} não encontrada na base de rastreio.") if entrega.nil?

    id     = client.resolver_id(entrega)
    visita = client.visita(id)

    render json: {
      ok: true,
      visita: {
        id:                  id,
        nf:                  visita['reference'],
        titulo:              visita['title'],
        endereco:            visita['address'],
        status:              visita['status'],
        checkout_observation: visita['checkout_observation'],
        checkout_comment:    visita['checkout_comment'],
        notes:               visita['notes'],
        checkout_time:       visita['checkout_time'],
        checkout_latitude:   visita['checkout_latitude'],
        checkout_longitude:  visita['checkout_longitude'],
        planned_date:        visita['planned_date'],
        contato:             visita['contact_name'],
        telefone:            visita['contact_phone'],
        # Fotos do card: fachada vem do rastreio (mesma foto do mapa de operações);
        # pictures/assinatura vêm da API. Só URLs http(s) chegam ao <img>.
        foto_fachada:        url_imagem(entrega.try(:foto_da_fachada)),
        pictures:            Array(visita['pictures']).filter_map { |u| url_imagem(u) },
        signature:           url_imagem(visita['signature']),
        motorista:           entrega.driver,
        veiculo:             entrega.vehicle
      },
      motivos: motivos_seguros
    }
  rescue SimpliRoute::NotFound => e
    render_erro(e.message)
  rescue SimpliRoute::Error => e
    render_erro("Erro ao consultar o SimpliRoute: #{e.message}", status: :bad_gateway)
  end

  # PATCH /admin/edicao_lancamento/atualizar
  # HTML: fluxo de form clássico (fallback). JSON: salvar-por-campo do card
  # (click-to-edit) — o fetch manda visit_id + só o campo editado.
  def atualizar
    authorize :edicao_lancamento

    id = params[:visit_id].to_s.strip
    return responder_erro('Visita não identificada.') if id.blank?

    anterior = client.visita(id)
    attrs    = mudancas(anterior)

    # Confirmar um valor idêntico não é erro no inline edit — só não há o que fazer.
    if attrs.empty?
      return respond_to do |format|
        format.json { render json: { ok: true, campos: [], visita: {} } }
        format.html { redirect_to admin_edicao_lancamento_path, alert: 'Nenhuma alteração informada.' }
      end
    end
    if attrs['status'].present? && STATUS_VALIDOS.exclude?(attrs['status'])
      return responder_erro('Status inválido.')
    end

    atualizada = client.atualizar_visita(id, attrs)

    AuditoriaLog.registrar(
      user:             current_user,
      acao:             'editar',
      entidade:         'SimpliRoute::Visita',
      entidade_id:      id,
      dados_anteriores: anterior.slice(*attrs.keys),
      dados_novos:      atualizada.slice(*attrs.keys),
      request:          request
    )

    respond_to do |format|
      format.json do
        render json: { ok: true, campos: attrs.keys, visita: atualizada.slice(*CAMPOS_EDITAVEIS) }
      end
      format.html do
        redirect_to admin_edicao_lancamento_path,
                    notice: "Lançamento da NF #{atualizada['reference']} atualizado no SimpliRoute. " \
                            'O painel refletirá na próxima sincronização.'
      end
    end
  rescue SimpliRoute::Error => e
    responder_erro("Não foi possível atualizar: #{e.message}", status: :bad_gateway)
  end

  # GET /admin/edicao_lancamento/historico?visit_id=123 → JSON com as DUAS fontes:
  # o histórico da API do SimpliRoute (best-effort, costuma vir vazio) e a nossa
  # trilha do AuditoriaLog (quem editou, quando, de→para).
  def historico
    authorize :edicao_lancamento

    id = params[:visit_id].to_s.strip
    interno = AuditoriaLog.por_entidade('SimpliRoute::Visita')
                          .where(entidade_id: id)
                          .includes(:user).recentes.limit(50)
                          .map do |log|
      {
        usuario: log.user&.nome_display || '—',
        acao:    log.acao,
        quando:  log.created_at.in_time_zone.strftime('%d/%m/%Y %H:%M'),
        de:      log.dados_anteriores,
        para:    log.dados_novos
      }
    end

    render json: { ok: true, api: client.historico(id), interno: interno }
  end

  private

  def client
    @client ||= SimpliRoute::Client.new
  end

  # Monta o hash de PATCH só com os campos que vieram no form E que realmente
  # mudaram em relação ao estado atual — PATCH mínimo, sem sobrescrever à toa.
  def mudancas(anterior)
    CAMPOS_EDITAVEIS.each_with_object({}) do |campo, memo|
      next unless params.key?(campo)

      novo = normalizar(campo, params[campo])
      next if equivalente?(campo, novo, anterior[campo])

      memo[campo] = novo
    end
  end

  # Evita PATCH falso: nil/"" são equivalentes; datas comparadas por instante;
  # lat/long por valor numérico (o reparse difere do texto original da API).
  def equivalente?(campo, novo, atual)
    return true if novo.to_s == atual.to_s

    case campo
    when 'checkout_time'
      a = (Time.zone.parse(novo.to_s) rescue nil)
      b = (Time.zone.parse(atual.to_s) rescue nil)
      a.present? && b.present? && a.to_i == b.to_i
    when 'checkout_latitude', 'checkout_longitude'
      novo.is_a?(Float) && atual.present? && (novo - atual.to_f).abs < 1e-9
    else
      false
    end
  end

  # Normaliza valores por campo. Campos em branco viram nil (permite LIMPAR o
  # motivo/comentário). checkout_time aceita datetime-local e vira ISO8601.
  def normalizar(campo, valor)
    valor = valor.to_s.strip
    return nil if valor.blank?

    case campo
    when 'checkout_latitude', 'checkout_longitude'
      Float(valor) rescue valor
    when 'checkout_time'
      (Time.zone.parse(valor)&.iso8601 rescue nil) || valor
    else
      valor
    end
  end

  # Lista de motivos; nunca quebra a tela se a API estiver fora.
  def motivos_seguros
    client.observations
  rescue SimpliRoute::Error
    []
  end

  def render_erro(msg, status: :unprocessable_entity)
    render json: { ok: false, erro: msg }, status: status
  end

  # Erro do atualizar nos dois formatos (JSON p/ inline edit, HTML p/ fallback).
  def responder_erro(msg, status: :unprocessable_entity)
    respond_to do |format|
      format.json { render json: { ok: false, erro: msg }, status: status }
      format.html { redirect_to admin_edicao_lancamento_path, alert: msg }
    end
  end

  # Só aceita URL http(s) — evita injetar lixo no <img src> do card (mesmo
  # filtro do foto_url do OperacaoMetricas).
  def url_imagem(valor)
    url = valor.to_s.strip
    url.match?(%r{\Ahttps?://}i) ? url : nil
  end

  # Sem token não há o que fazer — avisa e volta.
  def garantir_configurado
    return if SimpliRoute.configurado?

    msg = 'Integração com o SimpliRoute não configurada (defina SIMPLIROUTE_TOKEN no servidor).'
    respond_to do |format|
      format.html { redirect_to dashboard_path, alert: msg }
      format.json { render json: { ok: false, erro: msg }, status: :service_unavailable }
    end
  end
end
