class Base::ContaBancaria < ApplicationRecord
	has_paper_trail
	include TradutorConcern

	attr_accessor :banco_id
	attr_accessor :logado_na_contabilidade
	attr_accessor :exclusao_completa
	attr_accessor :orcamento_id
	attr_accessor :inativacao

	attr_default :conta_caixa_pcasp, false
	attr_default :tipo_de_conta_pcasp, :conta_unica
	attr_default :exclusao_completa, false
	attr_default :exibir_na_api, false

	belongs_to :agencia, inverse_of: :contas_bancarias, :autosave => true
	belongs_to :funcao
	belongs_to :convenio_bancario , class_name:"Contabilidade::ConvenioBancario"

	delegate :banco, to: :agencia

	has_many :contas_bancarias_por_unidade_orcamentaria, dependent: :destroy
	has_many :movimentacoes_da_conta_bancaria, through: :contas_bancarias_por_unidade_orcamentaria, source: :movimentacoes_da_conta_bancaria, class_name: 'Contabilidade::MovimentacaoDaContaBancaria'
	has_many :unidades_orcamentarias, class_name: 'Loa::UnidadeOrcamentaria', through: :contas_bancarias_por_unidade_orcamentaria
	has_many :fontes_de_recursos_da_conta_bancaria, class_name: 'Base::FonteDeRecursoDaContaBancaria', dependent: :destroy
	has_many :fontes_de_recursos, through: :fontes_de_recursos_da_conta_bancaria, source: :fonte_de_recurso, class_name: 'Base::FonteDeRecursos'
	has_many :contas_unidade_por_empenho, class_name: 'Base::ContaUnidadePorEmpenho', foreign_key: :conta_unidade_id, dependent: :restrict_with_exception
	has_many :contas_bancarias_por_pagamento, class_name: 'Base::ContaBancariaPorPagamento', foreign_key: :conta_bancaria_id, dependent: :restrict_with_exception
	has_many :empenhos, through: :contas_unidade_por_empenho, source: :empenho, inverse_of: :conta_credor, class_name: 'Contabilidade::Empenho', dependent: :restrict_with_exception
	has_many :liquidacoes, through: :empenhos, class_name: 'Contabilidade::Liquidacao'
	has_many :pagamentos, through: :contas_bancarias_por_pagamento, source: :pagamento, class_name: 'Contabilidade::Pagamento'
	has_many :transferencias, class_name: 'Obra::Transferencia', dependent: :restrict_with_exception
	has_many :retencoes, class_name: 'Contabilidade::Retencao', dependent: :restrict_with_exception
	has_many :fundos_de_investimento, class_name: 'Contabilidade::FundoDeInvestimento', dependent: :restrict_with_exception
	has_many :contas_pcasp_da_conta_bancaria_por_orcamento, class_name: 'Contabilidade::ContaPcaspDaContaBancariaPorOrcamento'

	accepts_nested_attributes_for :agencia, reject_if: :all_blank
	accepts_nested_attributes_for :fontes_de_recursos_da_conta_bancaria, reject_if: :all_blank, allow_destroy: true
	accepts_nested_attributes_for :contas_bancarias_por_unidade_orcamentaria, reject_if: :all_blank, allow_destroy: true

	validates_associated :agencia, if: Proc.new { !self.conta_caixa_pcasp? }
	validates_associated :fontes_de_recursos_da_conta_bancaria, if: Proc.new { !self.conta_caixa_pcasp? }
	validates_associated :contas_bancarias_por_unidade_orcamentaria, if: Proc.new { !self.conta_caixa_pcasp? }

	validates_presence_of :tipo_de_conta_pcasp
	validates_presence_of :numero_da_conta, if: Proc.new { !self.conta_caixa_pcasp? }
	validates_presence_of :tipo_de_conta, if: Proc.new { !self.conta_caixa_pcasp? && unidade_orcamentaria? }
	validates_presence_of :data_de_abertura, if: Proc.new { !self.conta_caixa_pcasp? && unidade_orcamentaria? }
	validates_presence_of :funcao_id, if: Proc.new { !self.conta_caixa_pcasp? && vinculada? }
	validates_presence_of :descricao_do_vinculo, if: Proc.new { !self.conta_caixa_pcasp? && vinculada? }
	validates_presence_of :fontes_de_recursos_da_conta_bancaria, if: Proc.new { !self.conta_caixa_pcasp? && self.logado_na_contabilidade }, message: "A conta bancaria precisa e no minimo uma fonte de recursos cadastrada"

	validates_uniqueness_of :numero_da_conta, scope: :agencia_id
	validates :fontes_de_recursos_da_conta_bancaria, uniq_nested_attributes: { atributo: :fonte_de_recurso_id, mensagem: "fonte de recurso deve ser única dentro de uma conta bancária" }

	validates_length_of :numero_da_conta, maximum: 12
	validates_length_of :operacao, maximum: 4, allow_blank: true
	validates_length_of :descricao_do_vinculo, maximum: 255
	validates_length_of :codigo_ug, is: 6, allow_blank: true
	validates_length_of :codigo_gestao, is: 5, allow_blank: true

	validates :data_de_abertura, date: true, allow_blank: true, unless: :unidade_orcamentaria?

	validate :valida_se_nao_possui_unidade_principal_marcada
	validate :soma_das_fontes_igual_ao_saldo_inicial, if: Proc.new { saldo_de_implantacao.to_d > 0 && saldo_de_implantacao_changed? && data_de_inativacao.blank? }

	before_validation :substitui_agencia_caso_ja_exista, unless: Proc.new { agencia.nil? }

	after_save :atualiza_saldo_inicial

	after_save :cria_conta_pcasp_da_conta_bancaria, if: Proc.new { tipo_de_conta_pcasp.present? }

	enum tipo: {
		pessoa: 0,
		unidade_orcamentaria: 1
	}

	enum tipo_de_conta: {
		conta_vinculada: 1,
		conta_nao_vinculada: 2
	}

	# vinculo da conta bancária com a conta do Plano de Contas (PCASP)
	# não foi criado um belongs_to pois a conta do pcasp é pra cada exercicio
	enum tipo_de_conta_pcasp: {
		conta_caixa: 0, #1.1.1.1.1.01.00 CAIXA
		conta_unica: 1, #1.1.1.1.1.02.00 CONTA ÚNICA
		conta_movimento_plano_financeiro: 2, #1.1.1.1.1.06.02	BANCOS CONTA MOVIMENTO - PLANO FINANCEIRO
		conta_movimento_plano_previdenciario: 3, #1.1.1.1.1.06.03	BANCOS CONTA MOVIMENTO - PLANO PREVIDENCIÁRIO
		conta_movimento_taxa_de_administracao: 4, #1.1.1.1.1.06.04	BANCOS CONTA MOVIMENTO - TAXA DE ADMINISTRAÇÃO

		conta_movimento_fundo_em_reparticao: 5, # Equivalente ao enum 2 a partir de 2022
		conta_movimento_fundo_em_capitalizacao: 6,# Equivalente ao enum 3 a partir de 2022

		conta_movimento_demais_contas: 7 #1.1.1.1.1.19.00 - BANCOS CONTA MOVIMENTO - DEMAIS CONTAS
	}

	def pode_excluir?
		!conta_caixa_pcasp? && !movimentacoes_da_conta_bancaria.any?
	end

	def pode_inativar?
		data_de_inativacao.blank?
	end

	def nomenclatura_e_numero
		if self.nomenclatura.blank?
			"#{self.try(:agencia).try(:banco).try(:sigla)} / #{self.numero_da_conta}"
		else
			"#{self.try(:agencia).try(:banco).try(:sigla)} / #{self.numero_da_conta} (#{self.nomenclatura})"
		end
	end

	def numero_sigla_nomeclatura
		if self.nomenclatura.blank?
			"#{self.numero_da_conta}/#{self.try(:agencia).try(:banco).try(:sigla)}"
		else
			"#{self.numero_da_conta}/#{self.try(:agencia).try(:banco).try(:sigla)} - #{self.nomenclatura}"
		end
	end

	def numero_e_sigla
		if self.nomenclatura.blank?
			"#{self.numero_da_conta} / #{self.try(:agencia).try(:banco).try(:sigla)}"
		else
			"#{self.numero_da_conta} / #{self.nomenclatura}"
		end
	end

	def sigla
		"#{self.try(:agencia).try(:banco).try(:sigla)}"
	end

	def numero_e_nomeclatura
		"#{self.numero_da_conta} (#{self.try(:nomenclatura)})"
	end

	def to_s
		if agencia.present?
			if self.nomenclatura.blank?
				"Conta: #{self.numero_da_conta} / Ag: #{self.agencia.numero_da_agencia.to_s} / #{self.agencia.banco&.sigla}"
			else
				"Conta: #{self.numero_da_conta} / Ag: #{self.agencia.numero_da_agencia.to_s} / #{self.agencia.banco&.sigla} - #{self.nomenclatura}"
			end
		else
			"Conta: #{self.nomenclatura}"
		end
	end

	def conta_e_numero_da_conta
		"Conta: #{self.numero_da_conta}"
	end

	def banco_agencia_conta
		if agencia.present?
			"#{self.agencia.try(:banco).try(:sigla).try(:upcase)} / Agência: #{self.agencia.try(:numero_da_agencia)} / Conta: #{self.numero_da_conta.to_s}"
		else
			"Conta: #{self.numero_da_conta.to_s}"
		end
	end

	def nomeclatura_com_unidade_principal_do_exercicio
		if self.unidade_orcamentaria_principal(contexto_atual.id).present?
			"#{self.to_s} (#{self.unidade_orcamentaria_principal(contexto_atual.id).try(:codigo_completo_e_sigla)})" 
		else
			self.to_s
		end
	end

	def codigo_completo_da_UG_e_Gestao
		"#{self.try(:codigo_ug)}#{self.try(:codigo_gestao)}"
	end

	def descricao_para_detalhe_do_pagamento
		self.to_s
	end

	def movimentacoes_do_exercicio(orcamento_atual)
		movimentacoes_da_conta_bancaria.joins(conta_bancaria_por_unidade_orcamentaria: {unidade_orcamentaria: :orgao} ).where('loa_orgaos.orcamento_id = ?',orcamento_atual.id)
	end

	def movimentacoes_do_exercicio_ate_data_definida(orcamento_atual, data)
		movimentacoes_da_conta_bancaria.joins(conta_bancaria_por_unidade_orcamentaria: {unidade_orcamentaria: :orgao} ).where('loa_orgaos.orcamento_id = ? and data_da_movimentacao <= ?', orcamento_atual.id, data)
	end

	def numero_e_nomenclatura
		resultado = [numero_da_conta]
		resultado.push("(#{nomenclatura})") if nomenclatura.present?
		return resultado.join(" ")
	end

	def unidade_orcamentaria_principal(orcamento_id = nil)
		if self.contas_bancarias_por_unidade_orcamentaria.present?
			if orcamento_id.to_i > 0
				self.contas_bancarias_por_unidade_orcamentaria.where(principal: true).map { |c| c.unidade_orcamentaria if c.unidade_orcamentaria.try(:orgao).try(:orcamento_id) == orcamento_id }.try(:compact).try(:first)
				else
				self.contas_bancarias_por_unidade_orcamentaria.find_by(principal: true).try(:unidade_orcamentaria)
			end
		end
	end

	def conta_bancaria_por_unidade_orcamentaria_principal(orcamento_id = nil)
		if self.contas_bancarias_por_unidade_orcamentaria.present?
			if orcamento_id.to_i > 0
				self.contas_bancarias_por_unidade_orcamentaria.where(principal: true).map { |c| c if c.unidade_orcamentaria.try(:orgao).try(:orcamento_id) == orcamento_id }.try(:compact).try(:first)
				else
				self.contas_bancarias_por_unidade_orcamentaria.find_by(principal: true)
			end
		end
	end

	def conta_por_unidade unidade_orcamentaria
		Base::ContaBancariaPorUnidadeOrcamentaria.find_by(conta_bancaria_id: self.id, unidade_orcamentaria_id: unidade_orcamentaria.id)
	end

	def vinculada?
		self.conta_vinculada?
	end

	def to_sim(data_referencia, exercicio_e_mes_de_geracao, orcamento, unidade_id)
		exercicio_do_orcamento = orcamento.exercicio.to_s
		unidade_orcamentaria = self.unidades_orcamentarias.find(unidade_id)
		saldo_da_conta = conta_por_unidade(unidade_orcamentaria).saldo_atual.to_f.to_s.sim_valor

		begin
			texto = ""
			texto << "106".sim_preenche(3) + ","
			texto << Configuracao.first.codigo_do_municipio_no_tcm.to_s.sim_preenche(3) + ","
			texto << exercicio_do_orcamento + '00' + ","
			texto << unidade_orcamentaria.try(:orgao).try(:codigo_com_zeros).to_s.sim_limite(2) + ","
			texto << unidade_orcamentaria.try(:codigo_formatado).to_s.codigo_uo_to_sim + ","
			texto << agencia.banco.numero_do_banco.to_s.sim_preenche(4) + ","
			texto << agencia.numero_da_agencia.to_s.delete(".\/-").first(4).sim_preenche(6) + ","
			texto << numero_da_conta.tr('-', '').tr('.', '').to_s.sim_preenche(10) + ","
			texto << exercicio_e_mes_de_geracao.to_s + ","
			texto << (data_de_abertura.present? && data_de_abertura.to_date.year ==  exercicio_do_orcamento ? data_de_abertura.sim_data : data_referencia.sim_data) + ","
			texto << saldo_da_conta + ","
			texto << self.read_attribute_before_type_cast(:tipo_de_conta).to_s + ","
			texto << funcao.try(:codigo).to_s.sim_limite(2) + ","
			texto << descricao_do_vinculo.to_s.sim_limite(255)

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

	def unidades_do_orcamento(orcamento)
		# MARCAR PARA TESTAR
		self.contas_bancarias_por_unidade_orcamentaria.joins(unidade_orcamentaria: :orgao).where("loa_orgaos.orcamento_id =?", orcamento.id)
	end

	def codigo_pcasp_baseado_no_tipo_de_conta
		case tipo_de_conta_pcasp
		when 'conta_caixa'
			'111110100'
		when 'conta_unica'
			'111110200'
		when 'conta_movimento_plano_financeiro', 'conta_movimento_fundo_em_reparticao'
			'111110602'
		when 'conta_movimento_plano_previdenciario', 'conta_movimento_fundo_em_capitalizacao'
			'111110603'
		when 'conta_movimento_taxa_de_administracao'
			'111110604'
		when 'conta_movimento_demais_contas'
			'111111900'
		end
	end

	def cria_conta_pcasp_da_conta_bancaria(orcamento = nil)
		if orcamento.present?
			orcamentos = Orcamento.order("exercicio ASC").where(id: orcamento.id).all
		else
			orcamentos = Orcamento.order("exercicio ASC").all
		end

		orcamentos.each do |orcamento|
			conta_pcasp = Contabilidade::Conta.find_by(orcamento_id: orcamento.id, codigo: codigo_pcasp_baseado_no_tipo_de_conta)

			if conta_pcasp.present?
				conta_pcasp_da_conta_bancaria = contas_pcasp_da_conta_bancaria_por_orcamento.find_by(orcamento_id: orcamento.id)

				if conta_pcasp_da_conta_bancaria.present?
					conta_pcasp_da_conta_bancaria.update(conta_id: conta_pcasp.id)
				else
					Contabilidade::ContaPcaspDaContaBancariaPorOrcamento.create(conta_id: conta_pcasp.try(:id), orcamento_id: orcamento.id, conta_bancaria_id: id)
				end
			end
		end
	end

	def valida_se_nao_possui_unidade_principal_marcada
		if self.contas_bancarias_por_unidade_orcamentaria.size > 1 && self.contas_bancarias_por_unidade_orcamentaria.map{ |f| f.principal == false }.all?
			errors.add(:base, 'Precisa selecionar uma unidade principal')
		end
	end

	def saldo_inicial_das_fontes
		fontes_do_exercicio_atual.reject(&:marked_for_destruction?).sum{ |fr| fr.saldo_inicial.to_d }
	end

	def soma_das_fontes_igual_ao_saldo_inicial
		saldo_inicial = saldo_de_implantacao.to_d > 0 ? saldo_de_implantacao.to_d : self.movimentacoes_da_conta_bancaria.do_exercicio_atual(contexto_atual).por_movimentacao_inicial&.first&.valor.to_d
		errors.add(:fontes, "Soma dos saldos iniciais deve ser igual #{saldo_de_implantacao.real_contabil}" ) if saldo_inicial_das_fontes != saldo_inicial.to_d
	end

	def fontes_do_exercicio_atual
		fontes_de_recursos_da_conta_bancaria.select { |frcb| frcb.fonte_de_recurso.try(:fonte_de_recurso_do_orcamento?, (Orcamento.find_by(id: orcamento_id))) }
	end

	def pode_informar_saldo_de_implantacao?
		if !self.persisted?
			return true
		else
			movimentacao_inicial = self.movimentacoes_da_conta_bancaria.do_exercicio_atual(contexto_atual).por_movimentacao_inicial.first
			if movimentacao_inicial.nil? || movimentacao_inicial&.valor.to_d == 0
				return true
			else
				return false
			end
		end
	end

	def possui_movimentacoes_no_ano(data)
		data_inicial = Date.new(data.year)
		movimentacoes_da_conta_bancaria.where('data_da_movimentacao >= ? AND data_da_movimentacao <= ?', data_inicial, data).any?
	end

	def saldo_inicial_por_data(data)
		movimentacoes_da_conta_bancaria.where('data_da_movimentacao <= ?', data).sum(:valor)
	end

	def saldo_atual_por_data(data)
		data_inicial = Date.new(data.year)
		movimentacoes_da_conta_bancaria.where('data_da_movimentacao >= ? AND data_da_movimentacao <= ?', data_inicial, data).sum(:valor)
	end

	def saldo_inicial_por_data_e_unidade(data, unidade_orcamentaria_id)
		contas_bancarias_por_unidade_orcamentaria.find_by(unidade_orcamentaria_id: unidade_orcamentaria_id).movimentacoes_da_conta_bancaria.where('data_da_movimentacao <= ?', data).sum(:valor)
	end

	def saldo_inicial_por_unidade(unidade_orcamentaria_id, data)
		self.contas_bancarias_por_unidade_orcamentaria.find_by(unidade_orcamentaria_id: unidade_orcamentaria_id).saldo_inicial_do_ano(data)
	end

	def saldo_atual
		self.contas_bancarias_por_unidade_orcamentaria.sum(:saldo_atual).to_d
	end

	def saldo_atual_por_data_e_unidade(data, unidade_orcamentaria_id)
		data_inicial = Date.new(data.year)
		contas_bancarias_por_unidade_orcamentaria.find_by(unidade_orcamentaria_id: unidade_orcamentaria_id).movimentacoes_da_conta_bancaria.where('data_da_movimentacao >= ? AND data_da_movimentacao <= ?', data_inicial, data).sum(:valor) rescue 0
	end

	def saldo_por_mes_e_unidade(data, unidade_orcamentaria_id)
		contas_bancarias_por_unidade_orcamentaria.find_by(unidade_orcamentaria_id: unidade_orcamentaria_id).movimentacoes_da_conta_bancaria.where('data_da_movimentacao <= ?', data.end_of_month).sum(:valor)
	end

	def saldo_do_exercicio(orcamento = nil)
		orcamento = contexto_atual unless orcamento.present?
		contas_bancarias_por_unidade_orcamentaria.where(unidade_orcamentaria: self.unidades_orcamentarias.joins(:orgao).where(loa_orgaos: { orcamento_id: orcamento.id})).sum(:saldo_atual)
	end

	def saldo_inicial
		self.movimentacoes_da_conta_bancaria.do_exercicio_atual(contexto_atual).por_movimentacao_inicial.sum(:valor) rescue 0
	end

	def atualiza_saldo_inicial
		if pode_informar_saldo_de_implantacao? && self.saldo_de_implantacao.to_d > 0
			unidade_principal = self.contas_bancarias_por_unidade_orcamentaria.joins(unidade_orcamentaria: :orgao).where("loa_orgaos.orcamento_id =?", contexto_atual.id).where(principal: true).first
			if unidade_principal.present?
				data_inicial = contexto_atual.present? ? Date.new(contexto_atual.exercicio.to_i, 1, 2) : Date.today
				data_inicial = data_inicial + 1.day if data_inicial.wday == 0 # se for sábado
				data_inicial = data_inicial + 2.day if data_inicial.wday == 6 # se for domingo
				unidade_principal.movimentacoes_da_conta_bancaria.create(
					valor: self.saldo_de_implantacao.to_f,
					historico: "Saldo inicial do Exercício.",
					modulo: unidade_principal,
					data_da_movimentacao: data_inicial
				)

				self.update_column(:saldo_de_implantacao, 0)
			end
		end
	end

	def substitui_agencia_caso_ja_exista
		agencia_existente = Base::Agencia.find_by(agencia.attributes.slice("numero_da_agencia", "orcamento_id", "banco_id"))
		self.agencia = agencia_existente if agencia_existente
	end

end
