class Contabilidade::AnulacaoDoEmpenho < ApplicationRecord
	has_paper_trail

	include IncrementadorDeCodigoConcern
	include VistoriavelAnulacoesDoEmpenhoConcern
	include GeradorDeEventosContabeis
	include SimConcern
	
	attr_accessor :criado_na_contabilidade
	attr_accessor :modulo_atual
	attr_accessor :logado_na_contabilidade
	attr_accessor :logado_no_administrativo

	attr_default :criado_na_contabilidade, false

	# reversor_de_eventos_contabeis acao: 5, atributo_pai: 'empenho', atributo_data: 'data_da_anulacao', atributo_codigo_movimentacao: 'numero_do_empenho'

	belongs_to :orcamento # usado apenas para nao repetir o numero da anulacao no exercicio
	belongs_to :empenho, required: true
	belongs_to :arquivo, class_name: 'Tcm::Arquivo', required: false

	delegate :possui_itens?, to: :empenho
	delegate :unidade_orcamentaria, :sub_elemento_de_despesa, to: :empenho


	has_many :itens_anulados, dependent: :destroy, inverse_of: :anulacao_do_empenho, class_name: "Contabilidade::AnulacaoDoItemDoEmpenho"

	accepts_nested_attributes_for :itens_anulados, reject_if: :all_blank, allow_destroy: true

	validates_presence_of :data_da_anulacao, :historico, :tipo_de_anulacao
	validates_presence_of :valor, if: Proc.new { self.empenho.itens_do_empenho.empty? }
	validates_associated :itens_anulados, on: :create_or_update
	validates :data_da_anulacao, date: true

	validate :valor_maior_que_o_saldo, on: :create
	validate :data_nao_pode_ser_anterior_a_data_do_empenho
	validate :valor_nao_pode_ser_maior_que_saldo_a_liquidar
	#validate :valor_da_anulacao_igual_ao_valor_dos_itens, if: Proc.new { self.parcial? && empenho && itens_anulados.size > 0 }
	validate :existe_numero_igual_nesse_orcamento
	validate :valida_se_ja_houve_envio_do_sim
	validate :valida_se_data_esta_no_orcamento

	validates :itens_anulados, uniq_nested_attributes: { atributo: :item_do_empenho_id, mensagem: "item deve ser único dentro da anulação"}

	after_initialize :popula_itens_anulados, if: Proc.new { new_record? && total? && possui_itens? }

	before_save :atualiza_tipo_de_anulacao, unless: Proc.new { self.status_was.eql?("confirmado") }
	before_create :atribui_codigo_disponivel, if: Proc.new { self.numero.to_s.empty? }
	before_update :atribui_codigo_disponivel, if: Proc.new { self.data_da_anulacao_changed? }
	
	# Lançamento do Orçamento da Despesa
	after_destroy :apagar_movimento_orcamentario
	after_save :lancar_movimento_orcamentario, unless: Proc.new { self.empenho.restos_a_pagar == true }


	after_create :cria_itens_da_anulacao, if: Proc.new{ self.itens_anulados.empty? }

	enum tipo_de_anulacao: {
		parcial: 0,
		total: 1
	}

	enum status: {
		solicitado: 0,
		enviado_para_controladoria: 1,
		enviado_para_contabilidade: 2,
		confirmado: 3,
		enviado_para_administrativo: 4,
		recebido: 5
	}

	scope :em_analise, -> { where(status: :enviado_para_controladoria) }
	scope :confirmadas, -> { where(status: :confirmado) }
	scope :confirmados, -> { where(status: :confirmado) }
	scope :do_orcamento, -> { joins(:empenho).where("contabilidade_empenhos.restos_a_pagar is false") }

	def numero_do_empenho
		empenho.numero_do_empenho
	end

	def detalhamento_do_plano_de_contas
		empenho.detalhamento_do_plano_de_contas
	end

	def enviado_ao_sim?
		self.mes_bloqueado? || (arquivo_id.present? && arquivo_id > 0 && arquivo.lote.lote_processado_ou_enviado?)
	end

	def pode_confirmar?
		(!envia_pra_controladoria? && !envia_pra_contabilidade? && self.logado_no_administrativo  == true) || self.logado_na_contabilidade  == true
	end

	# VALORES
	def valor_dos_itens
		if parcial?
			itens_anulados.valido.to_a.inject(0) { |total, item_anulado|
				total + (item_anulado.valid? && !item_anulado.marked_for_destruction? ? item_anulado.total.to_d : 0)
			}
		elsif total?
			itens_anulados.inject(0) { |total, item_anulado|
				total + item_anulado.item_do_empenho.total_anulado
			}
		end
	end

	def saldo_da_dotacao_antes_a_anulacao
		saldo_da_dotacao_apos_a_anulacao.to_f - self.valor.to_f
	end

	def saldo_da_dotacao_apos_a_anulacao
		lancamento_do_orcamento_da_despesa = Contabilidade::LancamentoDoOrcamentoDaDespesa.find_by(modulo: self)
		lancamento_do_orcamento_da_despesa.present? ? lancamento_do_orcamento_da_despesa.saldo_ate_o_lancamento.to_f : 0.to_f
	end

	# AÇÕES
	def atribui_codigo_disponivel
		if self.empenho.present?
			self.orcamento_id = self.empenho.orcamento_id
			gerar_codigo(data_da_anulacao, :numero, :data_da_anulacao, :orcamento_id, self.orcamento_id)
		end
	end

	def popula_itens_anulados
		empenho.itens_do_empenho.each do |item_do_empenho|
			if item_do_empenho.quantidade_disponivel_a_liquidar > 0
				itens_anulados.new(quantidade: item_do_empenho.quantidade_disponivel_a_liquidar, item_do_empenho: item_do_empenho, total: item_do_empenho.total_disponivel_a_liquidar.to_f)
			end
		end
	end

	def atualiza_tipo_de_anulacao
		empenho_totalmente_anulado = saldo_anulado.eql?(empenho.saldo.to_d) || empenho.saldo.to_d.eql?(0.0)

		if empenho.liquidacoes.empty? && empenho_totalmente_anulado
			self.tipo_de_anulacao = 'total'
		else
			self.tipo_de_anulacao = 'parcial'
		end
	end

	def saldo_anulado
		if empenho.diaria.present? || itens_anulados.empty?
			valor
		else
			itens_anulados.sum{ |item| item.total.to_d }
		end
	end

	def numero_anulacao_do_empenho
		self[:numero].to_i.digitos(8) if self[:numero].present?
	end

	def lancar_movimento_orcamentario
		ActiveRecord::Base.transaction do
			if self.empenho.present? && self.empenho.orcamento_da_despesa.present? && valor.to_d > 0 && self.confirmado?
				apagar_movimento_orcamentario
				self.empenho.orcamento_da_despesa.reload
				movimentacao = Contabilidade::LancamentoDoOrcamentoDaDespesa.new(
					orcamento_da_despesa: self.empenho.orcamento_da_despesa,
					data_do_lancamento: self.data_da_anulacao,
					valor: self.valor * -1,
					modulo: self
				)
				movimentacao.save
			end
		end
	end

	def apagar_movimento_orcamentario
		Contabilidade::LancamentoDoOrcamentoDaDespesa.find_by(modulo: self).try(:destroy)
	end

	# TCM
	def modalidade_anulacao
		if self.parcial?
			return "P"
		elsif self.total?
			return "T"
		end
	end

	def valida_se_ja_houve_envio_do_sim
		errors.add(:sim, 'O SIM do mês já foi enviado') if existe_lote_do_sim?
	end

	def valida_se_data_esta_no_orcamento
		if self.data_da_anulacao.present? && self.data_da_anulacao.try(:year) != self.empenho.try(:orcamento).try(:exercicio)
			errors.add(:data_da_anulacao, 'A data da anulação do empenho, não está dentro do orçamento do empenho.')
		end
	end

	def existe_lote_do_sim?
		ultima_data = data_da_anulacao

		if ultima_data.present?
			lote_sim = Tcm::Lote.find_by(
				orcamento_id: orcamento_id,
				tipo: [Tcm::Lote.tipos[:todos], Tcm::Lote.tipos[:contabilidade]],
				mes_de_referencia: ultima_data.month,
				situacao: [Tcm::Lote.situacoes[:gerado], Tcm::Lote.situacoes[:finalizado]]
			)

			return lote_sim.present?
		end

		return false
	end

	private
	# VALIDAÇÕES
	def valor_maior_que_o_saldo
		if self.valor && self.valor > 0
			errors.add(:valor, "não pode ser maior que o saldo do empenho") if empenho && (self.valor.to_f > self.empenho.saldo.to_f)
		end
	end

	def valor_nao_pode_ser_maior_que_saldo_a_liquidar
		if self.valor && self.valor > 0
			errors.add(:valor, "não pode ser maior que o saldo do empenho a liquidar.") if empenho && (self.valor.to_f > (self.empenho.definir_valor_do_empenho.to_f - self.empenho.valor_total_liquidado.to_f).round(2))
		end
	end

	def valor_da_anulacao_igual_ao_valor_dos_itens
		if self.valor && self.valor > 0
			errors.add(:valor, "deve ser igual à soma dos itens anulados") if self.valor.round(2) != valor_dos_itens.round(2)
		end
	end

	def data_nao_pode_ser_anterior_a_data_do_empenho
		if empenho.present? && !data_da_anulacao.nil?
			errors.add(:data_da_anulacao, "não pode ser anterior a data do empenho (#{empenho.data_do_empenho})") if data_da_anulacao < empenho.data_do_empenho
		end
	end

	def existe_numero_igual_nesse_orcamento
		if numero.present?
			anulacoes = Contabilidade::AnulacaoDoEmpenho.where(orcamento_id: self.orcamento_id, numero: self.numero).where.not(id: self.id)
			if anulacoes.present?
				anulacoes = anulacoes.select{|anulacao| !anulacao.empenho.restos_a_pagar}
				errors.add(:numero, "Número Já existe nesse exercicio") if anulacoes.any?
			end
		end
	end

	def cria_itens_da_anulacao
		self.empenho.itens_do_empenho.each do |item_do_empenho|
			Contabilidade::AnulacaoDoItemDoEmpenho.create(item_do_empenho_id: item_do_empenho.id, anulacao_do_empenho_id: self.id)
		end
	end

	def atribui_novo_valor_baseado_nos_itens
		self.valor = self.itens_anulados.sum(&:total_calculado)
	end

end
