require 'rails_helper'

RSpec.describe Contabilidade::Conta, type: :model do
	cria_grupos_de_conta

	it { is_expected.to belong_to :orcamento }
	it { is_expected.to belong_to :grupo_de_conta }
	it { is_expected.to have_many :contas_extra_orcamentaria }

	it { is_expected.to have_many :saldos_diarios_da_conta }
	it { is_expected.to have_many(:contas_por_eventos_contabeis).dependent(:restrict_with_exception) }
	it { is_expected.to have_many(:movimentacoes_do_plano_de_contas).through(:contas_por_eventos_contabeis) }

	it { is_expected.to have_many( :contas_por_unidades_orcamentarias ).dependent(:destroy) }
	it { is_expected.to have_many(:unidades_orcamentarias).through(:contas_por_unidades_orcamentarias) }

	[:orcamento_id, :grupo_de_conta_id, :nome, :codigo,
		:classe, :grupo, :subgrupo, :titulo, :subtitulo, :item, :subitem,
		:status, :natureza_do_saldo, :saldo
	].each { |atributo| it { is_expected.to validate_presence_of atributo } }

	it{ is_expected.to validate_uniqueness_of(:codigo).scoped_to(:orcamento_id).case_insensitive }
	it{ is_expected.to validate_uniqueness_of(:classe).scoped_to(:grupo, :subgrupo, :titulo, :subtitulo, :item, :subitem, :orcamento_id).case_insensitive }
	it{ is_expected.to validate_uniqueness_of(:grupo).scoped_to(:classe, :subgrupo, :titulo, :subtitulo, :item, :subitem, :orcamento_id).case_insensitive }
	it{ is_expected.to validate_uniqueness_of(:subgrupo).scoped_to(:classe, :grupo, :titulo, :subtitulo, :item, :subitem, :orcamento_id).case_insensitive }
	it{ is_expected.to validate_uniqueness_of(:titulo).scoped_to(:classe, :grupo, :subgrupo, :subtitulo, :item, :subitem, :orcamento_id).case_insensitive }
	it{ is_expected.to validate_uniqueness_of(:subtitulo).scoped_to(:classe, :grupo, :subgrupo, :titulo, :item, :subitem, :orcamento_id).case_insensitive }
	it{ is_expected.to validate_uniqueness_of(:item).scoped_to(:classe, :grupo, :subgrupo, :titulo, :subtitulo, :subitem, :orcamento_id).case_insensitive }
	it{ is_expected.to validate_uniqueness_of(:subitem).scoped_to(:classe, :grupo, :subgrupo, :titulo, :subtitulo, :item, :orcamento_id).case_insensitive }

	it { is_expected.to validate_length_of(:nome).is_at_most(255) }
	it { is_expected.to validate_length_of(:codigo).is_equal_to(9) }

	[:classe, :grupo, :subgrupo, :titulo, :subtitulo].each { |atributo| it { is_expected.to validate_length_of(atributo).is_equal_to(1) } }
	[:item, :subitem].each { |atributo| it { is_expected.to validate_length_of(atributo).is_equal_to(2) } }

	describe '#codigo_formatado' do
		it 'retorna o código formatado da conta' do
			conta = FactoryBot.create( :conta_patrimonial, :sem_validacao, importada_do_pcasp: true)

			expect(conta.codigo_formatado).to eq "1.1.1.1.2.00.00"
		end
	end

	describe '.buscar_analiticas' do
		it 'Retorna as contas que se encontram nos ultimos niveis detalhados' do
			conta_1 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "100000000", importada_do_pcasp: true)
			conta_2 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "110000000", importada_do_pcasp: true)
			conta_3 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "112000000", importada_do_pcasp: true)
			conta_4 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "120000000", importada_do_pcasp: true)
			conta_5 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "130000000", importada_do_pcasp: true)
			conta_6 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "131000000", importada_do_pcasp: true)
			conta_7 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "131600000", importada_do_pcasp: true)
			conta_8 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "180000000", importada_do_pcasp: true)
			conta_9 = FactoryBot.build( :conta_patrimonial, :sem_validacao, codigo: "182000000", importada_do_pcasp: true)
			[conta_1, conta_2, conta_3, conta_4, conta_5, conta_6,
			conta_7, conta_8, conta_9, ].each {|conta|
				conta.atribui_classificacao_da_conta
				conta.save(validate: false)
			}
			expect(Contabilidade::Conta.buscar_analiticas). to contain_exactly(conta_3, conta_4, conta_7, conta_9)
		end
	end

	describe '#codigo_formatado_e_nome' do
		it 'retorna o código formatado e o nome da conta' do
			conta = FactoryBot.create( :conta_patrimonial, :sem_validacao, importada_do_pcasp: true)

			expect(conta.codigo_formatado_e_nome).to eq "1.1.1.1.2.00.00 - CAIXA E EQUIVALENTES DE CAIXA EM MOEDA NACIONAL - INTRA OFSS"
		end
	end

	describe '#pode_detalhar_classificacao' do
		context 'quando a conta pode receber detalhamento' do
			it 'retorna true' do
				conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao )
				conta = FactoryBot.create( :conta_patrimonial )

				expect(conta.pode_detalhar_classificacao?).to eq true
			end
		end

		context 'quando a conta não pode receber detalhamento' do
			it 'retorna false' do
				conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao )
				conta = FactoryBot.create( :conta_patrimonial, importada_do_pcasp: true)

				expect(conta_pai.pode_detalhar_classificacao?).to eq false
			end
		end
	end

	describe "#classificacao_com_niveis_preenchidos" do
		it "retorna hash com os níveis preenchidos" do
			conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao )
			conta = FactoryBot.create( :conta_patrimonial, codigo: "111120000" )
			expect( conta.classificacao_com_niveis_preenchidos ).to have_key(:classe)
			expect( conta.classificacao_com_niveis_preenchidos ).to have_key(:grupo)
			expect( conta.classificacao_com_niveis_preenchidos ).to have_key(:subgrupo)
			expect( conta.classificacao_com_niveis_preenchidos ).to have_key(:titulo)
			expect( conta.classificacao_com_niveis_preenchidos ).to have_key(:subtitulo)
			expect( conta.classificacao_com_niveis_preenchidos ).not_to have_key(:item)
			expect( conta.classificacao_com_niveis_preenchidos ).not_to have_key(:subitem)
		end
	end

	describe "#classificacao_niveis_nao_preenchidos" do
		it "retorna hash com os níveis não preenchidos" do
			conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao )
			conta = FactoryBot.create( :conta_patrimonial, codigo: "111120000" )
			expect( conta.classificacao_niveis_nao_preenchidos ).not_to have_key(:classe)
			expect( conta.classificacao_niveis_nao_preenchidos ).not_to have_key(:grupo)
			expect( conta.classificacao_niveis_nao_preenchidos ).not_to have_key(:subgrupo)
			expect( conta.classificacao_niveis_nao_preenchidos ).not_to have_key(:titulo)
			expect( conta.classificacao_niveis_nao_preenchidos ).not_to have_key(:subtitulo)
			expect( conta.classificacao_niveis_nao_preenchidos ).to have_key(:item)
			expect( conta.classificacao_niveis_nao_preenchidos ).to have_key(:subitem)
		end
	end

	describe '#contas_filho' do
		it 'retorna as contas filho de uma conta' do
			conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao, importada_do_pcasp: false )
			conta_filho = FactoryBot.create( :conta_patrimonial, importada_do_pcasp: false )
			expect(conta_pai.contas_filho).to eq([conta_filho])
		end
	end

	describe '#determina_grupo_de_conta' do
		before(:each) do
			FactoryBot.create( :conta_patrimonial_pai, :sem_validacao )
		end
		context 'quando codigo começa com 1 até 4' do
			it 'associa com grupo de conta patrimonial' do

				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "111100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "111120000" )
				expect(conta.grupo_de_conta.nome).to eq "Patrimonial"
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "211100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "211120000" )
				expect(conta.grupo_de_conta.nome).to eq "Patrimonial"
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "311100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "311120000" )
				expect(conta.grupo_de_conta.nome).to eq "Patrimonial"
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "411100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "411120000" )
				expect(conta.grupo_de_conta.nome).to eq "Patrimonial"
			end
		end
		context 'quando codigo começa com 5 até 6' do
			it 'associa com grupo de conta orçamentária' do
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "511100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "511120000" )
				expect(conta.grupo_de_conta.nome).to eq "Orçamentária"
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "611100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "611120000" )
				expect(conta.grupo_de_conta.nome).to eq "Orçamentária"
			end
		end
		context 'quando codigo começa com 7 até 8' do
			it 'associa com grupo de conta controle' do
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "711100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "711120000" )
				expect(conta.grupo_de_conta.nome).to eq "Controle"
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "811100000")
				conta = FactoryBot.create( :conta_patrimonial, codigo: "811120000" )
				expect(conta.grupo_de_conta.nome).to eq "Controle"
			end
		end
	end

	describe '#deve_ter_pai_cadastrado' do
		context 'se nao tiver pai cadastrado' do
			it 'retorna erro' do
				conta = Contabilidade::Conta.create(FactoryBot.attributes_for( :conta_patrimonial, codigo: "111110000", importada_do_pcasp: false ))
				expect(conta.errors[:base]).to include 'não possui nível superior cadastrado'
			end
		end
		context 'se tiver pai cadastrado' do
			it 'salva' do
				conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao )
				conta = FactoryBot.create( :conta_patrimonial, importada_do_pcasp: false)
				expect(conta).to be_persisted
			end
		end
	end

	describe '#deve_ter_pai_ativo' do
		context 'se tiver pai inativo' do
			it 'retorna erro' do
				conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "111100000", status: "inativa")
				conta = Contabilidade::Conta.create(FactoryBot.attributes_for( :conta_patrimonial, codigo: "1", conta_pai_id: conta_pai.id ))
				expect(conta.errors[:base]).to include 'não pode derivar de uma conta inativa'
			end
		end
		context 'se tiver pai ativo' do
			it 'salva' do
				conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao, status: "ativa" )
				conta = Contabilidade::Conta.create(FactoryBot.attributes_for( :conta_patrimonial, codigo: "1", conta_pai_id: conta_pai.id ))
				expect(conta).to be_persisted
			end
		end
	end

	describe '#pai_nao_pode_ter_movimentacao' do
		before(:each) do
			@conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "311100000", analitica: true)
		end
		context 'se pai tiver movimentação' do
			it 'retorna erro' do
				conta_por_evento = Contabilidade::ContaPorEventoContabil.create(FactoryBot.attributes_for(:conta_patrimonial_evento_padrao, :credito, conta_id: @conta_pai.id))
				FactoryBot.create :movimentacao_empenho_padrao, conta_por_evento_contabil_id: conta_por_evento.id
				conta = Contabilidade::Conta.create( FactoryBot.attributes_for( :conta_patrimonial, codigo: "1", conta_pai_id: @conta_pai.id ))
				expect(conta.errors[:base]).to include 'não pode detalhar uma conta que já possui movimentações'
			end
		end
		context 'se pai não tiver movimentação' do
			it 'salva' do
				conta = Contabilidade::Conta.create(FactoryBot.attributes_for( :conta_patrimonial, codigo: "1", conta_pai_id: @conta_pai.id ))
				expect(conta).to be_persisted
			end
		end
	end

	describe '#muda_nivel_detalhado_da_conta_pai' do
		context 'se o pai já for do nível superior' do
			it 'não altera' do
				@conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "111100000", analitica: false)
				Contabilidade::Conta.create(FactoryBot.attributes_for( :conta_patrimonial, codigo: "1", conta_pai_id: @conta_pai.id ))
				expect( @conta_pai.analitica ).to be_falsey
			end
		end

		context 'se o pai estiver salvo como analítico' do
			it 'altera para superior' do
				@conta_pai = FactoryBot.create(:conta_patrimonial, :sem_validacao, codigo: "111100000", analitica: true)
				Contabilidade::Conta.create(FactoryBot.attributes_for( :conta_patrimonial, codigo: "1", conta_pai_id: @conta_pai.id ))
				@conta_pai.reload
				expect( @conta_pai.analitica? ).to be_falsey
			end
		end
	end

	describe '#save' do
		before(:each) do
			@conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao )
			@conta = FactoryBot.create( :conta_patrimonial, importada_do_pcasp: false )
		end

		it '#atribui_classificacao_da_conta' do
			expect(@conta.classe).to eq @conta.codigo[0]
			expect(@conta.grupo).to eq @conta.codigo[1]
			expect(@conta.subgrupo).to eq @conta.codigo[2]
			expect(@conta.titulo).to eq @conta.codigo[3]
			expect(@conta.subtitulo).to eq @conta.codigo[4]
			expect(@conta.item).to eq @conta.codigo[5..6]
			expect(@conta.subitem).to eq @conta.codigo[6..7]
		end

		it 'não permite alterações no orcamento_id' do
			@conta.orcamento_id = @conta.orcamento_id + 1
			@conta.save

			expect(@conta.errors[:orcamento_id]).to include 'não pode ser alterado'
		end

		describe 'com pai acima do quinto nível' do
			context 'se for passado um código de 2 dígitos' do
				it "retorna erro" do
					conta_filho = Contabilidade::Conta.create(codigo: "99", conta_pai_id: @conta_pai.id)

					expect(conta_filho.errors[:codigo]).to include 'não pode conter 2 digitos nesse nível'
				end
			end
			context 'se for passado um código de 1 dígito' do
				it "salva" do
					conta_filho = Contabilidade::Conta.create(FactoryBot.attributes_for(:conta_patrimonial, codigo: "9").merge(conta_pai_id: @conta_pai.id))
					expect(conta_filho).to be_persisted
				end
			end
		end

		describe 'com pai abaixo do quinto nível' do
			context 'se for passado um código de 2 dígitos' do
				it "salva" do
					conta_filho = Contabilidade::Conta.create(FactoryBot.attributes_for(:conta_patrimonial, codigo: "99").merge(conta_pai_id: @conta.id))
					expect(conta_filho).to be_persisted
				end
			end
			context 'se for passado um código de 1 dígito' do
				it "salva, com zero na frente do código incluido" do
					conta_filho = Contabilidade::Conta.create(FactoryBot.attributes_for(:conta_patrimonial, codigo: "9").merge(conta_pai_id: @conta.id))
					expect(conta_filho).to be_persisted
					expect(conta_filho.codigo).to eq (@conta.classificacao_com_niveis_preenchidos.values.join + "09").ljust(9, '0')
				end
			end
		end
	end

	describe '#nao_deve_ter_irmao_importado' do
		before(:each) do
			@conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao, codigo: "111100000", importada_do_pcasp: true )
		end
		context 'se conta pai já tiver filhos importados' do
			it 'retorna erro' do
				FactoryBot.create( :conta_patrimonial, codigo: "111110000", importada_do_pcasp: true )

				conta = Contabilidade::Conta.create(FactoryBot.attributes_for(:conta_patrimonial, codigo: "111120000", importada_do_pcasp: true ))
				expect(conta.errors[:codigo]).to include 'não pode ter irmãos padrões do sistema'
			end
		end
		context 'se conta pai não tiver filhos importados' do
			it 'salva' do
				FactoryBot.create( :conta_patrimonial, codigo: "111110000", importada_do_pcasp: false )
				conta = Contabilidade::Conta.create(FactoryBot.attributes_for( :conta_patrimonial, codigo: "111120000", importada_do_pcasp: true ))
				expect(conta).to be_persisted
			end
		end
	end

	describe '#update' do
		before(:each) do
			FactoryBot.create( :conta_patrimonial_pai, :sem_validacao, importada_do_pcasp: true )
			@conta = FactoryBot.create( :conta_patrimonial, importada_do_pcasp: true )
		end

		it '#deve_impedir_alteracao_para_contas_importadas_do_pcasp' do
			@conta.update(status: :inativa)

			expect(@conta.errors[:codigo]).to include 'não é possível alterar uma conta padrão do sistema'
		end
	end

	describe '#destroy' do
		it '#deve_impedir_delecao_para_contas_importadas_do_pcasp' do
			conta = FactoryBot.create( :conta_patrimonial, :sem_validacao, importada_do_pcasp: true )
			conta.destroy

			expect(conta.errors[:codigo]).to include 'não é possível remover uma conta padrão do sistema'
		end

		context '#deve_impedir_delecao_se_tiver_filhos' do
			before(:each) do
				@conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao, importada_do_pcasp: false )
				@conta_filho = FactoryBot.create( :conta_patrimonial, importada_do_pcasp: false )
			end
			it 'se tem filhos, não deleta' do
				expect { @conta_pai.destroy }.to raise_error('não é possível remover conta que tenha filhos')
			end
			it 'se não tem filhos, deleta' do
				expect { @conta_filho.destroy }.to change(Contabilidade::Conta, :count).by(-1)
			end
		end
	end

	describe '#atribui_natureza_do_saldo_da_conta_pai_para_novo_detalhamento' do
		it 'a natureza_do_saldo da conta_filho deve ser igual a da conta_pai' do
			conta_pai = FactoryBot.create( :conta_patrimonial_pai, :sem_validacao, importada_do_pcasp: false )
			conta_filho = FactoryBot.build( :conta_patrimonial, importada_do_pcasp: false, natureza_do_saldo: '', conta_pai_id: conta_pai.id )
			conta_filho.save

			expect(conta_filho.natureza_do_saldo).to eq conta_pai.natureza_do_saldo
		end
	end

	describe '#saldo_nao_pode_virar' do
		context 'quando a conta é credora' do
			it 'retorna mensagem de erro se o saldo virar para negativo' do
				conta = FactoryBot.build :conta_patrimonial, :sem_validacao, natureza_do_saldo: 'credor', saldo: (9.99 * -1), importada_do_pcasp: false
				conta.save

				expect(conta.errors[:saldo]).to include 'conta credora não pode ter saldo menor que zero'
			end
		end

		context 'quando a conta é devedora' do
			it 'retorna mensagem de erro se o saldo virar para positivo' do
				conta = FactoryBot.build :conta_patrimonial, :sem_validacao, natureza_do_saldo: 'devedor', saldo: 9.99, importada_do_pcasp: false
				conta.save

				expect(conta.errors[:saldo]).to include 'conta devedora não pode ter saldo maior que zero'
			end
		end
	end
end
