# app/models/consolidacao.rb
class Consolidacao < ApplicationRecord
  self.table_name = 'consolidacoes'

  belongs_to :criador,     class_name: 'User', foreign_key: :created_by
  belongs_to :finalizador, class_name: 'User', foreign_key: :finalizado_por, optional: true
  belongs_to :arquivador,  class_name: 'User', foreign_key: :arquivado_por,  optional: true

  has_many :consolidacao_motoristas, dependent: :destroy
  has_many :consolidacao_entregas,   dependent: :destroy

  # ── Enums ───────────────────────────────────────────────────
  enum status: { rascunho: 0, finalizada: 1, arquivada: 2 }

  # ── Soft delete ─────────────────────────────────────────────
  scope :ativas,     -> { where(deleted_at: nil) }
  scope :arquivadas, -> { where.not(deleted_at: nil) }

  # ── Validações ──────────────────────────────────────────────
  validates :nome,        presence: true
  validates :data_inicio, presence: true
  validates :data_fim,    presence: true
  validates :created_by,  presence: true
  validate  :periodo_valido

  # ── Callbacks ───────────────────────────────────────────────
  before_save :recalcular_valor_total

  # ── Escopos ─────────────────────────────────────────────────
  scope :recentes,           -> { order(created_at: :desc) }
  scope :no_periodo, ->(i, f) { where('data_inicio >= ? AND data_fim <= ?', i, f) }

  # ── Filtro por status de pagamento (derivado dos motoristas) ─
  # pago_em NÃO-nulo conta como pago; COUNT(pago_em) ignora os NULL.
  scope :pagamento_pago, -> {
    where(id: ConsolidacaoMotorista.group(:consolidacao_id)
                                   .having('COUNT(*) > 0 AND COUNT(*) = COUNT(pago_em)')
                                   .select(:consolidacao_id))
  }
  scope :pagamento_pendente, -> {
    where(id: ConsolidacaoMotorista.group(:consolidacao_id)
                                   .having('COUNT(pago_em) = 0')
                                   .select(:consolidacao_id))
  }
  scope :pagamento_parcial, -> {
    where(id: ConsolidacaoMotorista.group(:consolidacao_id)
                                   .having('COUNT(pago_em) > 0 AND COUNT(pago_em) < COUNT(*)')
                                   .select(:consolidacao_id))
  }

  # Consolidações que contêm QUALQUER das operações informadas (jsonb).
  # SEGURANÇA: os nomes passam por whitelist (Operacao.sanitizar) e por quote.
  scope :com_operacoes, ->(ops) {
    validas = Operacao.sanitizar(ops)
    next all if validas.empty?

    lista = validas.map { |o| connection.quote(o) }.join(',')
    where("EXISTS (SELECT 1 FROM jsonb_array_elements_text(COALESCE(operacoes, '[]'::jsonb)) e WHERE e IN (#{lista}))")
  }

  # ── Métodos ─────────────────────────────────────────────────

  # Arquivar é um soft-delete administrativo: deve SEMPRE funcionar, mesmo numa
  # consolidação zerada ou em rascunho com dados incompletos. Por isso usamos
  # update_columns — pula validações (periodo_valido, presença) e o callback
  # recalcular_valor_total, que não fazem sentido ao apenas mover para o arquivo.
  def arquivar!(user)
    update_columns(
      deleted_at:    Time.current,
      status:        self.class.statuses[:arquivada],
      arquivado_por: user&.id,
      arquivado_em:  Time.current,
      updated_at:    Time.current
    )
  end

  # Restaura uma consolidação arquivada. Como o status original não é guardado,
  # inferimos: se já tinha sido finalizada (finalizado_em presente) volta para
  # :finalizada, senão para :rascunho. Limpa os campos de arquivamento.
  # Também via update_columns para não depender da validade do conteúdo.
  def reativar!(_user)
    update_columns(
      deleted_at:    nil,
      status:        self.class.statuses[finalizado_em? ? :finalizada : :rascunho],
      arquivado_por: nil,
      arquivado_em:  nil,
      updated_at:    Time.current
    )
  end

  # ── Pagamento (status derivado dos motoristas da consolidação) ──
  # :pendente (nenhum pago) · :pago (todos pagos) · :parcial (alguns).
  def status_pagamento
    total = consolidacao_motoristas.count
    return :pendente if total.zero?

    pagos = consolidacao_motoristas.pagos.count
    return :pendente if pagos.zero?

    pagos >= total ? :pago : :parcial
  end

  def valor_pago
    consolidacao_motoristas.pagos.sum(:valor_total)
  end

  def valor_pendente
    consolidacao_motoristas.pendentes.sum(:valor_total)
  end

  def finalizar!(user)
    return false unless todas_entregas_classificadas?

    update!(
      status:         :finalizada,
      finalizado_por: user.id,
      finalizado_em:  Time.current
    )
  end

  # Só está completa quando CADA motorista tem todas as suas entregas elegíveis
  # classificadas. (Antes era uma soma agregada com >=, que mascarava motoristas
  # pendentes quando outro tinha entregas a mais.)
  def todas_entregas_classificadas?
    consolidacao_motoristas.all? do |cm|
      total = entregas_elegiveis_count(cm.motorista_nome)
      total.zero? || classificadas_count(cm.motorista_nome) >= total
    end
  end

  def percentual_classificado
    return 0 if total_entregas_estimadas.zero?
    ((consolidacao_entregas.count.to_f / total_entregas_estimadas) * 100).round
  end

  def total_entregas_estimadas
    @total_entregas_estimadas ||= consolidacao_motoristas.sum do |cm|
      entregas_elegiveis_count(cm.motorista_nome)
    end
  end

  # Entregas pagas elegíveis para um motorista no período, respeitando o filtro
  # de veículos da consolidação (quando houver). Fonte única usada pelo wizard,
  # pela revisão e pelo gate de finalização.
  def entregas_elegiveis_count(motorista)
    Entrega.contar_pagas(
      inicio:    data_inicio,
      fim:       data_fim,
      motorista: motorista,
      vehicle:   vehicle_ids.presence,
      operacoes: operacoes.presence
    )
  end

  # Rótulos amigáveis das operações vinculadas (para exibição).
  def operacoes_label
    Array(operacoes).map { |t| Operacao.label(t) }
  end

  # Entrega conta como classificada se tiver ≥1 pilar (distinct por tracking_id,
  # já que agora pode haver vários pilares por entrega).
  # Apontamentos manuais (manual: true) NÃO entram aqui: eles vivem fora do
  # conjunto elegível do período, então contá-los inflaria o progresso para
  # além de 100% (e quebraria o gate classificadas >= elegíveis).
  def classificadas_count(motorista)
    consolidacao_entregas.where(motorista_nome: motorista, manual: false)
                         .distinct.count(:tracking_id)
  end

  # ── Apontamento manual ──────────────────────────────────────
  # Adiciona uma entrega "por fora" (NF que chegou depois) com um ou mais pilares
  # (tipos). O motorista vem da própria entrega e é incluído na consolidação se
  # ainda não estiver. Retorna o nome do motorista. Idempotente por (tracking, tipo).
  def adicionar_apontamento!(entrega, tipos, user)
    tipos_validos = Array(tipos).map(&:to_s).select { |t| ConsolidacaoEntrega.tipos.key?(t) }.uniq
    raise ArgumentError, 'Selecione ao menos uma classificação.' if tipos_validos.empty?

    motorista = entrega.driver.to_s.strip
    raise ArgumentError, 'Entrega sem motorista — não é possível apontar.' if motorista.blank?

    transaction do
      consolidacao_motoristas.find_or_create_by!(motorista_nome: motorista)

      tipos_validos.each do |tipo|
        ce = consolidacao_entregas.find_or_initialize_by(tracking_id: entrega.tracking_id, tipo: tipo)
        next if ce.persisted? # pilar já existe — mantém

        ce.assign_attributes(
          motorista_nome: motorista,
          valor_aplicado: ConsolidacaoEntrega.valor_para(tipo),
          manual:         true,
          created_by:     user&.id
        )
        ce.save!
      end

      recalcular_motorista!(motorista)
    end

    motorista
  end

  # Recalcula o valor de um motorista (soma dos pilares, desconto subtrai) e o
  # total da consolidação. Fonte única usada pelo wizard e pelos apontamentos.
  def recalcular_motorista!(nome)
    cm = consolidacao_motoristas.find_by(motorista_nome: nome)
    return unless cm

    total = consolidacao_entregas.where(motorista_nome: nome).sum do |e|
      e.desconto? ? -e.valor_aplicado : e.valor_aplicado
    end
    cm.update_column(:valor_total, total)
    update_column(:valor_total, consolidacao_motoristas.sum(:valor_total))
  end

  private

  def periodo_valido
    return unless data_inicio && data_fim
    errors.add(:data_fim, 'deve ser após a data de início') if data_fim < data_inicio
  end

  def recalcular_valor_total
    self.valor_total = consolidacao_motoristas.sum(:valor_total)
  end
end
