class Contabilidade::TransferenciaFinanceira < ApplicationRecord
	has_paper_trail

  include TradutorConcern
	include AASM
	include MensagemConcern
	include IncrementadorDeCodigoConcern
	include GeradorDeEventosContabeis
	include SimConcern


	attr_default :documento_bancario, :outro
	attr_default :tipo_de_movimento, :original

	attr_accessor :unidade_orcamentaria_origem, :unidade_orcamentaria_destino, :conta_origem, :conta_destino

  belongs_to :orcamento, class_name: 'Orcamento'
  belongs_to :conta_bancaria_origem, class_name: 'Base::ContaBancariaPorUnidadeOrcamentaria', foreign_key: :conta_bancaria_origem_id
  belongs_to :conta_bancaria_destino, class_name: 'Base::ContaBancariaPorUnidadeOrcamentaria', foreign_key: :conta_bancaria_destino_id
	belongs_to :fonte_de_recurso_origem, class_name: 'Base::FonteDeRecursos', foreign_key: :fonte_de_recurso_origem_id
	belongs_to :fonte_de_recurso_destino, class_name: 'Base::FonteDeRecursos', foreign_key: :fonte_de_recurso_destino_id
	belongs_to :conta_do_grupo_de_retencoes_do_controle_de_pg, class_name: "Contabilidade::ContaDoGrupoDeRetencoesDoControleDePg"
	belongs_to :pagamento_da_retencao, class_name: "Contabilidade::PagamentoDaRetencao"

	has_many :pagamentos_do_lote_bancario, class_name: "Contabilidade::PagamentoDoLoteBancario"

	validates_presence_of :data ,:tipo_de_transferencia, :valor, :conta_bancaria_origem_id, :conta_bancaria_destino_id, :documento_bancario, :tipo_de_movimento, :historico, :fonte_de_recurso_origem_id, :fonte_de_recurso_destino_id

	validates_numericality_of :valor, greater_than: 0

	validates :data, data_nao_pode_estar_no_futuro: true
	validates :data, sabado_ou_domingo_ou_feriado: true
	validates :data, date: true

	validate :verifica_unidade_orcamentaria_do_repasse_do_duodecimo
	validate :verifica_se_data_esta_dentro_do_exercicio_logado
	validate :contas_bancarias_nao_podem_ser_iguais, if: Proc.new { self.conta_bancaria_origem.present? && self.conta_bancaria_destino.present? }
	validate :verifica_saldo_da_conta_origem, if: proc { self.conta_bancaria_origem.present? && self.valor.positive? }
	validate :verifica_saldo_da_conta_destino, on: :update, if: proc { self.valor_changed? && self.conta_bancaria_destino.present? && self.valor.positive? }
	validate :valida_conta_do_grupo_de_retencoes_do_controle_de_pg
	validate :valida_se_possui_lote_do_sim_gerado

	before_destroy :valida_saldo_da_conta_bancaria_destino_antes_de_excluir, if: proc { self.confirmado? && self.conta_bancaria_destino.present? }

	after_commit :gera_numero_da_transferencia

	after_save :lancar_na_conta_bancaria, if: Proc.new { self.confirmado? }
	#after_save :debitar_da_conta_bancaria_de_origem, if: Proc.new { self.solicitado? && !unidades_tem_mesmo_responsavel? }
	#after_save :creditar_da_conta_bancaria_de_destino, if: Proc.new { self.confirmado? }

	after_destroy :apagar_lancamento_na_conta

	scope :transferencia_de_repasse_do_duodecimo_para_o_portal, -> {where("tipo_de_transferencia in (?) AND status = ?", [Contabilidade::TransferenciaFinanceira.tipos_de_transferencia[:repasse_de_duodecimo_da_camara], Contabilidade::TransferenciaFinanceira.tipos_de_transferencia[:recebimento_de_duodecimo_da_camara]], Contabilidade::TransferenciaFinanceira.status[:confirmado])}

	enum documento_bancario: {
    cheque: 0,
    outro: 1
  }

	enum tipo_de_transferencia: {
		repasse_de_duodecimo_da_camara: 1,
		recebimento_de_duodecimo_da_camara: 2,
		transferencia_entre_contas_mesma_ug: 3,
		concessao_de_transferencia_para_execucao_orcamentaria: 4,
		recebimento_de_transferencia_para_execucao_orcamentaria: 5,
		concessao_transferencia_para_execucao_extraorcamentaria_e_restos_a_pagar: 6,
		recebimento_de_transferencia_para_execucao_extraorcamentaria_e_restos_a_pagar: 7,
		concessao_de_transferencia_para_aporte_de_recursos_ao_rpps: 8,
		recebimento_de_transferencia_para_aporte_de_recursos_ao_rpps: 9,
		devolucao_de_duodecimo_da_camara: 10
	}

	enum tipo_de_plano_financeiro: {
		recursos_para_cobertura_insuficiencias_financeiras: 0,
		recursos_para_formacao_de_reserva: 1,
		outros_aportes_para_o_rpps: 2
	}

	enum tipo_de_plano_previdenciario: {
		recusos_para_cobertura_de_deficit_financeiro: 0,
		recursos_para_cobertura_de_deficit_aportes: 1,
		recursos_para_cobertura_de_deficit_aliquota: 2,
		outros_aportes_para_o_rpps_prev: 3
	}

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

	enum status: {
		solicitado: 0,
		confirmado: 1
	}

	aasm column: :status, enum: true, whiny_transitions: false do
		state :solicitado, :initial => true
		state :confirmado

		event :confirmar do
			transitions from: :solicitado, to: :confirmado do
				guard do
					!unidades_tem_mesmo_responsavel? && solicitado?
				end
			end
		end
	end

	def self.tipos_de_transferencia_mapeados
		tipos_de_transferencia.collect do |t|
			["#{'%02d' % t[1]} - #{localizar('tipo_de_transferencia', t[0])}", t[0]]
		end
	end

	def unidades_tem_mesmo_responsavel?
		if conta_bancaria_origem.present? && conta_bancaria_destino.present?
			!conta_bancaria_origem.unidade_orcamentaria.unidade_gestora.responsavel_contabil_diferente && !conta_bancaria_destino.unidade_orcamentaria.unidade_gestora.responsavel_contabil_diferente
		else
			false
		end
	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

	def to_sim(data_referencia)
		begin
			texto = ""

			texto << "607".sim_preenche(3) + "," #1
			texto << Configuracao.first.codigo_do_municipio_no_tcm.sim_preenche(3) + "," #2
			texto << data.to_date.sim_data + "," #3
			texto << self.numero_da_transferencia.last(2).to_s.sim_preenche(2) + "," #4
			texto << (self.estorno? ? "E" : "T").sim_limite(1) + "," #5

			if conta_bancaria_origem.conta_bancaria.conta_caixa_pcasp?
				texto << (conta_bancaria_origem.unidade_orcamentaria.tipo_de_unidade_administrativa.executivo? ? "1000".sim_limite(4) : "2000".sim_limite(4)) + "," #6
				texto << "".sim_preenche(6) + "," #7
				texto << "".sim_preenche(10) + "," #8
			else
				texto << conta_bancaria_origem.conta_bancaria.agencia.banco.numero_do_banco.to_s.sim_preenche(4) + "," #6
				texto << conta_bancaria_origem.conta_bancaria.agencia.numero_da_agencia.to_s.delete(".\/-").first(4).sim_preenche(6)	+ "," #7
				texto << conta_bancaria_origem.conta_bancaria.numero_da_conta.gsub("-",'').gsub(".",'').sim_preenche(10) + "," #8
			end

			texto << '"' + self.numero_da_transferencia.to_s + '",' #9

			if conta_bancaria_destino.conta_bancaria.conta_caixa_pcasp?
				texto << (conta_bancaria_destino.unidade_orcamentaria.tipo_de_unidade_administrativa.executivo? ? "1000".sim_limite(4) : "2000".sim_limite(4)) + "," #10
				texto << "".sim_preenche(6) + "," #11
				texto << "".sim_preenche(10) + "," #12
			else
				texto << conta_bancaria_destino.conta_bancaria.agencia.banco.numero_do_banco.to_s.sim_preenche(4) + "," #10
				texto << conta_bancaria_destino.conta_bancaria.agencia.numero_da_agencia.to_s.delete(".\/-").first(4).sim_preenche(6)	+ "," #11
				texto << conta_bancaria_destino.conta_bancaria.numero_da_conta.gsub("-",'').gsub(".",'').sim_preenche(10) + "," #12
			end

			texto << valor.to_f.to_s.sim_valor + "," #13
			texto << historico.sim_descricao.sim_limite(255) + "," #14
			texto << (self.cheque? ? "1" : "2") + "," #15

			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_da_transferencia, atributo_falho, coluna)
			else
				raise e
			end
		end
	end

	def ordem_de_envio_formatado
		"#{conta_bancaria_origem.unidade_orcamentaria.sigla} X #{conta_bancaria_destino.unidade_orcamentaria.sigla}"
	end

	def unidade_destino_igual_origem?
		unidade_destino == unidade_origem
	end

	def transferencia_de_repasse_ou_recebimento_do_duodecimo?
		self.tipo_de_transferencia == "repasse_de_duodecimo_da_camara" || self.tipo_de_transferencia == "recebimento_de_duodecimo_da_camara"
	end

	def saldo_final_da_conta_bancaria_valido?
		self.conta_bancaria_origem.conta_bancaria.reload.saldo_atual_por_data(self.data).to_d < 0 || self.conta_bancaria_origem.conta_bancaria.reload.saldo_do_exercicio(contexto_atual).to_d < 0 ? false : true
	end

	private
	def contas_bancarias_nao_podem_ser_iguais
		return if conta_bancaria_origem.conta_bancaria.blank? || conta_bancaria_destino.conta_bancaria.blank?
		return if (conta_bancaria_origem.conta_bancaria_id != conta_bancaria_destino.conta_bancaria_id) ||
			(conta_bancaria_destino.unidade_orcamentaria_id != conta_bancaria_origem.unidade_orcamentaria_id && conta_bancaria_origem.conta_bancaria_id == conta_bancaria_destino.conta_bancaria_id)

		errors.add(:conta_bancaria_destino_id, "deve ser diferente da conta bancária de origem")
	end

	def verifica_se_data_esta_dentro_do_exercicio_logado
		errors.add(:data, "data deve estar dentro do exercício logado") if contexto_atual.present? && self.data.present? && self.contexto_atual.exercicio.to_i != self.data.year.to_i
	end

	def verifica_unidade_orcamentaria_do_repasse_do_duodecimo
		if self.transferencia_de_repasse_ou_recebimento_do_duodecimo? && !(self.conta_bancaria_destino.unidade_orcamentaria.tipo_de_unidade_administrativa.legislativo?)
				errors.add(:unidade_orcamentaria_destino, "Transferencias do tipo repasse/recebimento do duocecimo devem ser direcionados a unidades orçamentarias do legislativo")
		end
	end

	def verifica_saldo_da_conta_origem
		saldo_inicial = conta_bancaria_origem.saldo_por_data(self.data).to_d + valor_was.to_d - self.valor.to_d
		if saldo_inicial < 0
			errors.add(:valor, "Saldo insuficiente para a conta (origem) selecionada, saldo disponível na data selecionada será: #{saldo_inicial.to_d.real_contabil}") if valor.to_d > saldo_inicial
		else
			conta_bancaria_origem.soma_das_movimentacoes_futuras_agrupadas_por_dia(self.data).each do |movimentacao|
				saldo_inicial = saldo_inicial + movimentacao['valor'].to_d
				errors.add(:valor, "Saldo insuficiente para a conta (origem) selecionada, saldo disponível em #{I18n.l(Date.parse(movimentacao['data_da_movimentacao']))} será: #{saldo_inicial.to_d.real_contabil}") if valor.to_d > saldo_inicial
			end
		end
	end

	def verifica_saldo_da_conta_destino
		saldo_inicial = conta_bancaria_destino.saldo_por_data(self.data).to_d + valor_was.to_d - self.valor.to_d
		if saldo_inicial < 0
			errors.add(:valor, "Saldo insuficiente para a conta (destino) selecionada, saldo disponível na data selecionada será: #{saldo_inicial.to_d.real_contabil}") if valor.to_d > saldo_inicial
		else
			conta_bancaria_destino.soma_das_movimentacoes_futuras_agrupadas_por_dia(self.data).each do |movimentacao|
				saldo_inicial = saldo_inicial + movimentacao['valor'].to_d
				errors.add(:valor, "Saldo insuficiente para a conta (destino) selecionada, saldo disponível em #{I18n.l(Date.parse(movimentacao['data_da_movimentacao']))} será: #{saldo_inicial.to_d.real_contabil}") if valor.to_d > saldo_inicial
			end
		end
	end

	def valida_saldo_da_conta_bancaria_destino_antes_de_excluir
		saldo_atual = conta_bancaria_destino.saldo_atual - self.valor.to_d
		saldo_do_dia_do_lancamento = conta_bancaria_destino.saldo_por_data(self.data) - self.valor.to_d

		raise "O saldo atual da conta bancária não poderá ficar negativo." if saldo_do_dia_do_lancamento < 0
		raise "O saldo da conta bancária no dia do lançamento não poderá ficar negativo" if saldo_atual < 0
	end

	def gera_numero_da_transferencia
		if self.numero_da_transferencia.nil? || self.numero_da_transferencia.to_s.empty? || self.data.to_s.gsub("/", "")[0..3] != self.numero_da_transferencia.to_s[0..3]
			gerar_codigo(data, :numero_da_transferencia, :data, :orcamento_id, self.orcamento_id)
			self.update_column(:numero_da_transferencia, self.numero_da_transferencia)
		end
	end

	def lancar_na_conta_bancaria
		return if conta_bancaria_origem.blank? || conta_bancaria_destino.blank? || valor <= 0

		movimentacao_params = { modulo: self, modulo_id: self.id, data_da_movimentacao: self.data }
		movimentacao_origem_params = movimentacao_params.merge(conta_bancaria_por_unidade_orcamentaria_id: conta_bancaria_origem.id)
		movimentacao_destino_params = movimentacao_params.merge(conta_bancaria_por_unidade_orcamentaria_id: conta_bancaria_destino.id)

		ActiveRecord::Base.transaction do
			movimentacao_origem = Contabilidade::MovimentacaoDaContaBancaria.find_by(movimentacao_origem_params.except(:data_da_movimentacao))

			if movimentacao_origem.present?
				if (movimentacao_origem.valor != self.valor) || self.conta_bancaria_origem_id_changed?
					movimentacao_origem.try(:destroy)
					movimentacao_origem = Contabilidade::MovimentacaoDaContaBancaria.new(movimentacao_origem_params)
					movimentacao_origem.valor = (self.valor * - 1)
					movimentacao_origem.historico = "Transferência Financeira para #{self.conta_bancaria_destino.numero_agencia_e_banco}"

					movimentacao_origem.save!
				else
					movimentacao_origem.update_column(:data_da_movimentacao, data)
					movimentacao_origem.update_column(:historico, "Transferência Financeira para #{self.conta_bancaria_destino.numero_agencia_e_banco}")
				end
			else
				movimentacao_origem = Contabilidade::MovimentacaoDaContaBancaria.find_or_initialize_by(movimentacao_origem_params)
				movimentacao_origem.valor = (self.valor * -1)
				movimentacao_origem.historico = "Transferência Financeira para #{self.conta_bancaria_destino.numero_agencia_e_banco}"
				
				movimentacao_origem.save
			end

			movimentacao_destino = Contabilidade::MovimentacaoDaContaBancaria.find_by(movimentacao_destino_params.except(:data_da_movimentacao))

			if movimentacao_destino.present?
				if (movimentacao_destino.valor != self.valor) || self.conta_bancaria_destino_id_changed?
					movimentacao_destino.try(:destroy)
					movimentacao_destino = Contabilidade::MovimentacaoDaContaBancaria.new(movimentacao_destino_params)
					movimentacao_destino.valor = self.valor
					movimentacao_destino.historico = "Recebimento de Transferência da #{self.conta_bancaria_origem.numero_agencia_e_banco}"

					movimentacao_destino.save!
				else
					movimentacao_destino.update_column(:data_da_movimentacao, data)
					movimentacao_destino.update_column(:historico, "Recebimento de Transferência da #{self.conta_bancaria_origem.numero_agencia_e_banco}")
				end
			else
				movimentacao_destino = Contabilidade::MovimentacaoDaContaBancaria.find_or_initialize_by(movimentacao_destino_params)
				movimentacao_destino.valor = self.valor
				movimentacao_destino.historico = "Recebimento de Transferência da #{self.conta_bancaria_origem.numero_agencia_e_banco}"

				movimentacao_destino.save
			end
		end
	end

	def debitar_da_conta_bancaria_de_origem
		return if conta_bancaria_origem.blank? || valor <= 0
		
		movimentacao_params = { modulo: self, modulo_id: self.id, data_da_movimentacao: self.data }
		movimentacao_origem_params = movimentacao_params.merge(conta_bancaria_por_unidade_orcamentaria_id: conta_bancaria_origem.id)
		ActiveRecord::Base.transaction do
			movimentacao_origem = Contabilidade::MovimentacaoDaContaBancaria.find_or_initialize_by(movimentacao_origem_params)
			movimentacao_origem.valor = (self.valor * -1)
			movimentacao_origem.historico = historico
			cria_mensagem_de_transferencia_a_confirmar self
		end
	end

	def creditar_da_conta_bancaria_de_destino
		return if conta_bancaria_destino.blank? || valor <= 0
		movimentacao_params = { modulo: self, modulo_id: self.id, data_da_movimentacao: self.data }
		movimentacao_destino_params = movimentacao_params.merge(conta_bancaria_por_unidade_orcamentaria_id: conta_bancaria_destino.id)
		
		ActiveRecord::Base.transaction do
			movimentacao_destino = Contabilidade::MovimentacaoDaContaBancaria.find_or_initialize_by(movimentacao_destino_params)
			movimentacao_destino.valor = self.valor
			movimentacao_destino.historico = historico
			movimentacao_destino.save
		end
	end

	def apagar_lancamento_na_conta
		movimentacoes_da_conta_bancaria = Contabilidade::MovimentacaoDaContaBancaria.where(modulo_id: self.id, modulo_type: self.class.to_s)

		if movimentacoes_da_conta_bancaria.present?
			movimentacoes_da_conta_bancaria.destroy_all
		end
	end


	def valida_conta_do_grupo_de_retencoes_do_controle_de_pg
		if self.conta_do_grupo_de_retencoes_do_controle_de_pg.present?
			errors.add(:valor, "deve ser (#{self.conta_do_grupo_de_retencoes_do_controle_de_pg.valor_do_grupo.valor_financeiro}) conforme retençao lançada no controle de pagamentos") if self.valor.to_f != self.conta_do_grupo_de_retencoes_do_controle_de_pg.valor_do_grupo.to_f
		end
	end

	def valida_se_possui_lote_do_sim_gerado
		errors.add(:sim, "O SIM do mês já foi enviado") if lote_do_sim_gerado?(self.data)
	end
	
	def unidade_destino
    conta_bancaria_destino.unidade_orcamentaria
  end

  def unidade_origem
    conta_bancaria_origem.unidade_orcamentaria
  end
end
