class Contabilidade::DespesaExtraOrcamentaria < ApplicationRecord
	has_paper_trail
	include IncrementadorDeCodigoConcern
	include TradutorConcern
	include GeradorDeEventosContabeis

	#gerador_de_eventos_contabeis codigo: 3, atributo_data: 'data_de_emissao', atributo_codigo_movimentacao: 'numero_de_caixa'

	attr_accessor :conta_bancaria_id
	attr_accessor :semiautomatico
	attr_accessor :lancamento_automatico_retencao

	attr_default :lancamento_automatico_retencao, false

	belongs_to :orcamento, required: true
	belongs_to :conta_extra_orcamentaria, required: true
	belongs_to :conta_bancaria_por_unidade_orcamentaria, class_name: 'Base::ContaBancariaPorUnidadeOrcamentaria', required: true
	belongs_to :pessoa, class_name: 'Base::Pessoa', required: true, foreign_key: 'credor_id'
	belongs_to :unidade_orcamentaria, class_name: 'Loa::UnidadeOrcamentaria'
	belongs_to :pagamento_da_retencao, class_name: 'Contabilidade::PagamentoDaRetencao'
	belongs_to :fonte_de_recursos, class_name: 'Base::FonteDeRecursos'

	has_many :lancamentos_extraorcamentario_despesa, as: :modulo
	has_many :pagamentos_do_lote_bancario, class_name: "Contabilidade::PagamentoDoLoteBancario"

	has_one :estorno_de_despesa_extra_orcamentaria, dependent: :restrict_with_exception
	has_one :conta_bancaria, through: :conta_bancaria_por_unidade_orcamentaria, class_name: 'Base::ContaBancaria'

	after_initialize :set_conta_bancaria_id_e_unidade_orcamentaria_id

	before_validation :set_conta_bancaria_por_unidade_orcamentaria

	validates_presence_of :orcamento_id, :data_de_emissao, :conta_extra_orcamentaria_id, :conta_bancaria_por_unidade_orcamentaria_id, :credor_id, :tipo_de_documento, :valor_da_despesa
	validates_presence_of :numero_do_documento, if: Proc.new { self.cheque? }
	validates_presence_of :mes_de_competencia, :ano_de_competencia, if: :obriga_mes_e_ano_de_competencia?
	validates_presence_of :fonte_de_recursos_id, unless: Proc.new { self.lancamento_automatico_retencao == true || self.pagamento_da_retencao_id.present? }
	validates_numericality_of :valor_da_deducao, greater_than: 0, allow_nil: true
	validates_numericality_of :valor_juros_e_multa, greater_than: 0, allow_nil: true
	validates_numericality_of :valor_da_despesa, greater_than: 0

	validate :verifica_saldo_da_conta_bancaria, if: Proc.new {self.valor_da_despesa_changed? || self.conta_bancaria_por_unidade_orcamentaria_id_changed?}
	validate :verifica_saldo_conta_extra, if: Proc.new {self.conta_extra_orcamentaria.present?}
	validate :valida_despesa_com_data_futura
	validate :valida_data_com_exercicio_logado
	validate :valida_se_ja_houve_envio_do_sim
	
	after_commit :atribui_codigo_disponivel

	after_save :gera_movimentacao_conta_bancaria
	after_save :gera_lancamento_extraorcamentario

	after_destroy :desfaz_movimentacao_conta_bancaria
	after_destroy :apagar_movimento_extraorcamentario

	enum tipo_de_lancamento: {
		original: 0,
		estorno: 1,
	}

	enum tipo_de_documento: {
		cheque: 1,
		outro_tipo_de_documento_de_credito: 2,
		lote_bancario: 3,
		caixa: 9
	}

	enum mes_de_competencia: {
    janeiro: 1,
    fevereiro: 2,
    marco: 3,
    abril: 4,
    maio: 5,
    junho: 6,
    julho: 7,
    agosto: 8,
    setembro: 9,
    outubro: 10,
    novembro: 11,
    dezembro: 12
  }

	ransacker :valor_total do
		Arel.sql("(coalesce(valor_da_deducao, 0) + coalesce(valor_da_despesa, 0) + coalesce(valor_juros_e_multa, 0))")
	end

	def verifica_saldo_conta_extra
		# if conta_extra_orcamentaria.contas_extra_por_unidades_orcamentarias.find_by(unidade_orcamentaria_id: unidade_orcamentaria_id).saldo < valor_da_despesa.to_f + valor_juros_e_multa.to_f
		# 	errors.add(:conta_extra_orcamentaria_id, "Saldo insuficiente")
		# end
		unless self.conta_extra_orcamentaria.ativo?
			if self.unidade_orcamentaria_id.present? && self.data_de_emissao.present?
				total_da_despesa = self.valor_da_despesa.to_d + valor_juros_e_multa.to_d
				saldo_atual = self.conta_extra_orcamentaria.saldo_por_unidade_orcamentaria(self.unidade_orcamentaria_id) + self.valor_da_despesa_was.to_d + self.valor_juros_e_multa_was.to_d
				saldo_do_dia_do_lancamento = self.conta_extra_orcamentaria.saldo_por_unidade_orcamentaria_e_data(self.unidade_orcamentaria_id, self.data_de_emissao) + self.valor_da_despesa_was.to_d + self.valor_juros_e_multa_was.to_d

				errors.add(:conta_extra_orcamentaria_id, "(Conta Extra) Saldo insuficiente, saldo disponível na data selecionada #{saldo_do_dia_do_lancamento.to_d.real_contabil}") if total_da_despesa > saldo_do_dia_do_lancamento
				errors.add(:conta_extra_orcamentaria_id, "(Conta Extra) Saldo insuficiente, saldo atual da conta: #{saldo_atual.to_d.real_contabil}") if total_da_despesa > saldo_atual
			end
		end
	end

	def verifica_saldo_da_conta_bancaria
		if self.conta_bancaria_por_unidade_orcamentaria.present?
			total_da_despesa = self.valor_da_despesa.to_d + valor_juros_e_multa.to_d
			saldo_atual = conta_bancaria_por_unidade_orcamentaria.saldo_atual.to_d + self.valor_da_despesa_was.to_d + self.valor_juros_e_multa_was.to_d
			saldo_do_dia_do_lancamento = conta_bancaria_por_unidade_orcamentaria.saldo_por_data(self.data_de_emissao).to_d + self.valor_da_despesa_was.to_d + self.valor_juros_e_multa_was.to_d

			errors.add(:conta_bancaria_por_unidade_orcamentaria_id, "(Conta Bancária) Saldo insuficiente, saldo disponível na data selecionada #{saldo_do_dia_do_lancamento.to_d.real_contabil}") if total_da_despesa > saldo_do_dia_do_lancamento
			errors.add(:conta_bancaria_por_unidade_orcamentaria_id, "(Conta Bancária) Saldo insuficiente, saldo atual da conta: #{saldo_atual.to_d.real_contabil}") if total_da_despesa > saldo_atual
		end
	end

	def valida_despesa_com_data_futura
		errors.add(:data_de_emissao, 'A data selecionada não pode ser maior que a data atual') if (self.data_de_emissao.present? && self.data_de_emissao > Date.today)
	end

	def valida_data_com_exercicio_logado
		errors.add(:data_de_emissao, "A data selecionada está fora do exercicio logado") if (self.data_de_emissao.present? && contexto_atual.present? && self.data_de_emissao.year.to_i != contexto_atual.exercicio.to_i)
	end
	
	def atribui_codigo_disponivel
		if self.numero_de_caixa.nil? || self.numero_de_caixa.to_s.empty? || self.data_de_emissao.to_s.gsub("/", "")[0..3] != self.numero_de_caixa.to_s[0..3]
			numeros_pagamentos = self.orcamento.pagamentos.where(data_da_solicitacao: self.data_de_emissao).pluck(:numero).map(&:to_i)
			numeros_despesa = self.orcamento.despesas_extra_orcamentarias.where(data_de_emissao: self.data_de_emissao).pluck(:numero_de_caixa).map(&:to_i) 
			todos_os_numeros = numeros_despesa + numeros_pagamentos
			ultimo_numero_gerado = [ultimo_numero_gerado.to_i, todos_os_numeros.max].max if ultimo_numero_gerado && todos_os_numeros.present?
		
			numero_gerado = ultimo_numero_gerado ? (ultimo_numero_gerado.to_i + 1) : gerar_codigo(data_de_emissao, :numero_de_caixa, :data_de_emissao, :orcamento_id, self.orcamento_id)
		
			numero_disponivel = (numero_gerado.to_i..Float::INFINITY).lazy.find do |numero|
				!todos_os_numeros.include?(numero.to_i) && self.numero_de_caixa != numero.to_i
			end
		
			self.numero_de_caixa = numero_disponivel.to_s.rjust(8, '0')
			self.update_column(:numero_de_caixa, self.numero_de_caixa)
		end
	end
	
	def valida_se_ja_houve_envio_do_sim
		if existe_lote_do_sim?
			errors.add(:data_de_emissao, 'O SIM do mês já foi enviado')
		elsif self.mes_bloqueado?
			errors.add(:data_de_emissao, 'O mês do objeto está bloqueado, solicite o desbloqueio ao administrador')
		end
	end

	def existe_lote_do_sim?
		ultima_data = data_de_emissao

		if ultima_data.present?
			orcamento_id = Orcamento.find_by_exercicio(ultima_data.year)
			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

	def numero_formatado
		self[:numero_de_caixa].to_i.digitos(8) if self[:numero_de_caixa].present?
	end

	def unidade_orcamentaria
		conta_bancaria_por_unidade_orcamentaria.try(:unidade_orcamentaria)
	end

	def set_conta_bancaria_por_unidade_orcamentaria
		if unidade_orcamentaria_id.present? && conta_bancaria_id.present?
			self.conta_bancaria_por_unidade_orcamentaria_id = Base::ContaBancariaPorUnidadeOrcamentaria.find_by(unidade_orcamentaria_id: unidade_orcamentaria_id, conta_bancaria_id: conta_bancaria_id).try(:id)
		end
	end

	def set_conta_bancaria_id_e_unidade_orcamentaria_id
		if conta_bancaria_por_unidade_orcamentaria.present?
			self.unidade_orcamentaria_id = conta_bancaria_por_unidade_orcamentaria.unidade_orcamentaria_id
			self.conta_bancaria_id = conta_bancaria_por_unidade_orcamentaria.conta_bancaria_id
		end
	end

	def valor_total
		valor_da_despesa.to_f + valor_da_deducao.to_f + valor_juros_e_multa.to_f
	end

	def enviado_para_o_sim?
		self.arquivo_id.present? && self.arquivo_id > 0
	end

	def movimentacoes_da_conta_bancaria
		conta_bancaria_por_unidade_orcamentaria.movimentacoes_da_conta_bancaria
	end

	def dados_da_movimentacao_bancaria
		tipo_de_imposto = self.try(:pagamento_da_retencao).try(:retencao).present? ? "#{' - ' + self.pagamento_da_retencao.retencao.descricao_imposto + ' - '}" : ''
		{
			historico: "Pagamento Extraorçamentario #{self.numero_de_caixa} #{tipo_de_imposto} Conta: #{conta_bancaria_por_unidade_orcamentaria.conta_bancaria.to_s} - Credor: #{pessoa.try(:nome)}",
			modulo: self,
			data_da_movimentacao: data_de_emissao
		}
	end

	def to_sim(data_referencia)
		begin
			exercicio_do_orcamento = orcamento.exercicio.to_s
			conta_bancaria = conta_bancaria_por_unidade_orcamentaria.conta_bancaria

			texto = ""
			texto << "614".sim_preenche(3) + ","  #1
			texto << Configuracao.first.codigo_do_municipio_no_tcm.sim_preenche(3) + "," #2
			texto << exercicio_do_orcamento.sim_limite_sem_aspas(4, "00") + "," #3
			texto << unidade_orcamentaria.orgao.codigo.sim_preenche(2) + ","
			texto << unidade_orcamentaria.codigo.codigo_uo_to_sim + ","
			texto << conta_extra_orcamentaria.codigo_para_o_sim(unidade_orcamentaria.id) + ","
	
			if conta_bancaria.agencia.present?
				texto << conta_bancaria.agencia.banco.numero_do_banco.to_s.sim_preenche(4) + "," #7
				texto << conta_bancaria.agencia.numero_da_agencia.to_s.delete(".\/-").first(4).sim_preenche(6) + "," #8
			else
				texto << "9999" + "," #7
				texto << "999999" + "," #8
			end

			texto << numero_da_conta_bancaria_formatado.to_s.sim_preenche(10) + "," #9
			texto << numero_de_caixa.to_s.sim_limite(10) + "," #10 
			texto << data_referencia.to_s + "," #11
			texto << numero_de_caixa.to_s.sim_limite(10) + "," #12
			texto << data_de_emissao.sim_data + "," #13
			texto << valor_da_despesa.to_f.to_s.sim_valor + "," #14
			texto << (valor_da_deducao.present? ? valor_da_deducao.to_f.to_s.sim_valor : "0.00") + "," #15

			tipo_de_documento_para_o_sim = self.read_attribute_before_type_cast(:tipo_de_documento).to_i
			tipo_de_documento_para_o_sim = 2 if tipo_de_documento_para_o_sim == 3

			texto << tipo_de_documento_para_o_sim.to_s + "," #16
			texto << pessoa.nome.sim_limite(60) + "," #17
			texto << data_referencia.to_s + "," #18
			texto << valor_da_despesa.to_f.to_s.sim_valor + "," #19
			texto << valor_juros_e_multa.to_f.to_s.sim_valor #20
			return texto
		rescue => e
			if e.class.to_s == "NoMethodError"
				atributo_falho = e.message.split(" ")[2]
				coluna = CSV.parse(texto, :headers => false).last.count
				raise e.mensagem_traduzida(self, self.numero_de_caixa, atributo_falho, coluna)
			else
				raise e
			end
		end
	end

	def numero_da_conta_bancaria_formatado
		conta_bancaria_por_unidade_orcamentaria.conta_bancaria.numero_da_conta.tr(".", "").tr("-","")
	end

	def esta_em_um_lote?
		self.pagamentos_do_lote_bancario.joins(:lote_bancario).where(contabilidade_lotes_bancarios: { status: [1, 2]}).size > 0 ? true : false
	end

	private
	def desfaz_movimentacao_conta_bancaria
		movimentacoes_da_despesa = Contabilidade::MovimentacaoDaContaBancaria.where(modulo_id: self.id, modulo_type: "Contabilidade::DespesaExtraOrcamentaria")
		movimentacoes_da_despesa.each do |movimentacao|
			movimentacao.try(:destroy)
		end
	end

	def gera_movimentacao_conta_bancaria
		return false if conta_bancaria_por_unidade_orcamentaria.blank?

		movimentacoes_da_despesa = Contabilidade::MovimentacaoDaContaBancaria.where(modulo_id: self.id, modulo_type: "Contabilidade::DespesaExtraOrcamentaria")
		#movimentacoes_da_despesa_anteriores = movimentacoes_da_despesa.where.not(conta_bancaria_por_unidade_orcamentaria_id: self.conta_bancaria_por_unidade_orcamentaria_id)

		movimentacoes_da_despesa.each do |movimentacao|
			movimentacao.try(:destroy)
		end

		movimentacao_ativa = movimentacoes_da_conta_bancaria.find_or_create_by!(dados_da_movimentacao_bancaria)

		if movimentacao_ativa.valor.present? && movimentacao_ativa.valor != valor_total
			movimentacao_ativa.try(:destroy)
			movimentacao_ativa = movimentacoes_da_conta_bancaria.create!(dados_da_movimentacao_bancaria)
		end

		movimentacao_ativa.update_attribute(:valor, valor_total * -1)
	end

	def gera_lancamento_extraorcamentario
		ActiveRecord::Base.transaction do
			if valor_total.to_f > 0
				if lancamentos_extraorcamentario_despesa.any?
					lancamentos_extraorcamentario_despesa.each do |lancamento|
						apagar_movimento_extraorcamentario
						cria_lancamentos_da_conta_extraorcamentaria
					end
				else
					cria_lancamentos_da_conta_extraorcamentaria
				end
			end
		end
	end

	def cria_lancamentos_da_conta_extraorcamentaria
		movimentacao = Contabilidade::LancamentoExtraorcamentarioDespesa.new(
			conta_extra_orcamentaria_id: self.conta_extra_orcamentaria_id,
			data_do_lancamento: self.data_de_emissao,
			valor: valor_total,
			modulo: self
		)
		movimentacao.save
	end

	def apagar_movimento_extraorcamentario
		lancamentos_extraorcamentario_despesa.try(:destroy_all)
	end

	def obriga_mes_e_ano_de_competencia?
    Configuracao.last.obriga_competencia_em_despesas_extra_orcamentarias
  end
end
