require 'rails_helper'

RSpec.describe Ppa::Iniciativa, type: :model do
	cria_tipos_de_despesa

	it{ is_expected.to belong_to :objetivo }
	it{ is_expected.to belong_to :sub_funcao }
	it{ is_expected.to belong_to :funcao }
	it{ is_expected.to belong_to :natureza_da_iniciativa }
	it{ is_expected.to belong_to :unidade_orcamentaria }
	it{ is_expected.to belong_to :proposta }

	it{ is_expected.to have_many(:projecoes_de_despesa).dependent(:destroy) }

	[:descricao, :objetivo_id, :sub_funcao_id, :funcao_id, :unidade_orcamentaria_id].each do |atributo|
		it{ is_expected.to validate_presence_of atributo}
	end

	describe '#validate_uniqueness_of :proposta_id' do
		subject {
			FactoryBot.create( :ppa_iniciativa_com_proposta )
		}

		it { is_expected.to validate_uniqueness_of :proposta_id }
	end

	describe "validação da descrição da iniciativa" do
		it{
			iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )
			allow_any_instance_of( Ppa::Iniciativa ).to receive(:ppa).and_return( iniciativa.ppa )
			is_expected.to validate_uniqueness_of( :descricao ).scoped_to( :objetivo_id )
		}
	end

	describe "validação do código da iniciativa" do
		context "no #update" do
			it "não reclama da unicidade do seus próprio código" do
				iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental, codigo: '001' )
				iniciativa.descricao = 'atualizando iniciativa'
				iniciativa.save
				expect( iniciativa.errors[:codigo] ).to_not include 'já está em uso'
			end
		end
		context "quando há uma iniciativa com o mesmo código no escopo de natureza da iniciativa" do
			context "em um mesmo PPA" do
				it "adiciona erro de validação" do
					FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental, codigo: '001' )

					outra_iniciativa = FactoryBot.build(:ppa_iniciativa_para_ensino_fundamental, codigo: '001', descricao: 'outra iniciativa')

					outra_iniciativa.save
					expect( outra_iniciativa.errors[:codigo] ).to include 'já está em uso'
				end
			end

			context "em PPAs diferentes" do
				it "não adiciona erro de validação" do
					FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental, codigo: '001' )

					outra_iniciativa = FactoryBot.build(
						:ppa_iniciativa_para_ensino_fundamental,
						codigo: '001',
						descricao: 'outra iniciativa',
						objetivo_id: FactoryBot.create(
							:ppa_objetivo,
							orgao_id: FactoryBot.create( :orgao_secretaria_da_fazenda, :ppa_2010 ).id,
							programa_id: FactoryBot.create( :programa_escolar, :ppa_2010 ).id
						).id
					)

					outra_iniciativa.save
					expect( outra_iniciativa.errors[:codigo] ).to_not include 'já está em uso'

				end
			end
		end

		context "quando há uma iniciativa com o mesmo código mas com naturezas diferentes" do
			it "não adiciona erro de valição" do
				FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental, codigo: '001' )

				outra_iniciativa = FactoryBot.build(
					:ppa_iniciativa_para_ensino_fundamental,
					codigo: '001',
					descricao: 'outra iniciativa',
					natureza_da_iniciativa_id: FactoryBot.create(:projeto).id
				)

				outra_iniciativa.save
				expect( outra_iniciativa.errors[:codigo] ).to_not include 'já está em uso'

			end
		end
	end

	describe "#codigo_com_zeros" do
		context "quando o código é menor que 100" do
			it "retorna o código com os zeros necessário à esquerda" do
				iniciativa = Ppa::Iniciativa.new
				iniciativa.codigo = 4
				expect( iniciativa.codigo_com_zeros ).to eq '004'
				iniciativa.codigo = 40
				expect( iniciativa.codigo_com_zeros ).to eq '040'
			end
		end

		context "quando o código é maior que 100" do
			it "retorna o código sem zeros à esquerda" do
				iniciativa = Ppa::Iniciativa.new
				iniciativa.codigo = 105
				expect( iniciativa.codigo_com_zeros ).to eq '105'
			end
		end
	end

	describe "#codigo_completo" do
		it "retorna o código da natureza + o código da iniciativa" do
			natureza_da_iniciativa =  Ppa::NaturezaDaIniciativa.new(codigo: 2)
			iniciativa = Ppa::Iniciativa.new( natureza_da_iniciativa: natureza_da_iniciativa )
			iniciativa.codigo = 1
			expect( iniciativa.codigo_completo ).to eq '2001'
		end
	end

	describe "unidade orçamentária" do
		cria_tipos_de_despesa
		it "pertence ao mesmo PPA da iniciativa" do
			iniciativa = FactoryBot.build(:ppa_iniciativa_para_ensino_fundamental)
			unidade_orcamentaria_de_outro_ppa = FactoryBot.create(
				:unidade_orcamentaria_administracao, orgao: FactoryBot.create(:orgao_secretaria_da_fazenda, :ppa_2010)
			)
			iniciativa.unidade_orcamentaria = unidade_orcamentaria_de_outro_ppa
			iniciativa.save
			expect( iniciativa.errors[:unidade_orcamentaria_id] ).to include 'deve pertencer ao mesmo PPA'
		end
	end

	describe "ao criar uma iniciativa" do

		context "com o ppa configurado para detalhar as despesas no programa" do
			it "não cria nenhuma projeção de despesa" do
				iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental)
				expect( iniciativa.projecoes_de_despesa ).to be_empty
			end

			context "com o programa sem despesas criadas para a unidade gestora da iniciativa" do
				before do
					@iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )
					@programa = @iniciativa.programa
				end

				it "cria quatro despesas correntes para o programa com cada exercicio do seu PPA" do
					@programa.ppa.periodo.each do |exercicio|
						expect(
							@programa.projecoes_de_despesa.despesas_correntes.find_by(
								exercicio: exercicio ,
								unidade_orcamentaria_id: @iniciativa.unidade_orcamentaria_id
							)
						).to be_a( Ppa::ProjecaoDeDespesa )
					end
				end

				it "cria quatro despesas de capital para o programa com cada exercicio do seu PPA" do
					@programa.ppa.periodo.each do |exercicio|
						expect(
							@programa.projecoes_de_despesa.despesas_de_capital.find_by(
								exercicio: exercicio,
								unidade_orcamentaria_id: @iniciativa.unidade_orcamentaria_id
							)
						).to be_a(Ppa::ProjecaoDeDespesa)
					end
				end

				it "tem todas as projeções de despesa geradas com o valor zerado" do
					soma_das_despesas = @programa.projecoes_de_despesa.sum(:valor)
					expect( soma_das_despesas ).to be_zero
				end

				it "tem todas as projeções de despesa geradas com a mesma unidade orçamentária da iniciativa" do
					@programa.projecoes_de_despesa.each do |despesa|
						expect( despesa.unidade_orcamentaria_id ).to eq @iniciativa.unidade_orcamentaria_id
					end
				end
			end

			context "com o programa tendo outras iniciativas para a mesma unidade orçamentária" do
				it "não altera nada" do
					iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )
					FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental,
						codigo: '002',
						descricao: 'outra iniciativa'
					)
					programa = iniciativa.programa

					expect(
						programa.projecoes_de_despesa.where( unidade_orcamentaria_id: iniciativa.unidade_orcamentaria_id ).size
					).to eq 8
				end
			end
		end

		context "quando o seu ppa está configurado para detalhar as despesas nas iniciativas" do
			before do
				@iniciativa = FactoryBot.build(:ppa_iniciativa_para_ensino_fundamental)
				@iniciativa.ppa.detalhar_despesas_nas_iniciativas!
				@iniciativa.save
			end

			it "cria quatro projeções de despesa do tipo 'Despesa Corrente'" do
				expect( @iniciativa.despesas_correntes.count ).to eq 4
			end

			it "cria despesas correntes com valores zerados" do
				@iniciativa.despesas_correntes.each do |despesa|
					expect( despesa.valor ).to be_zero
				end
			end

			it "cria uma despesa corrente com o exercício igual ao de seu ppa" do
				periodo = @iniciativa.ppa.periodo
				expect(@iniciativa.despesas_correntes.count).to eq periodo.to_a.count
				periodo.each do |ano|
					expect( @iniciativa.despesas_correntes.find_by(exercicio: ano) ).not_to be_nil
				end
			end

			it "cria uma despesa corrente com a unidade orçamentária igual a da sua iniciativa" do
				@iniciativa.despesas_correntes.each do |despesa|
					expect( despesa.unidade_orcamentaria ).to eq @iniciativa.unidade_orcamentaria
				end
			end

			it "cria uma projeção de despesa do tipo 'Despesa de Capital'" do
				expect( @iniciativa.despesas_de_capital.count ).to eq 4
			end

			it "cria despesas de capital com valores zerados" do
				@iniciativa.despesas_de_capital.each do |despesa|
					expect( despesa.valor ).to be_zero
				end
			end

			it "cria uma despesa de capital com o exercício igual ao de seu ppa" do
				periodo = @iniciativa.ppa.periodo
				expect(@iniciativa.despesas_de_capital.count).to eq periodo.to_a.count
				periodo.each do |ano|
					expect( @iniciativa.despesas_de_capital.find_by(exercicio: ano) ).not_to be_nil
				end
			end

			it "cria uma despesa de capital com a unidade orçamentária igual a da sua iniciativa" do
				@iniciativa.despesas_de_capital.each do |despesa|
					expect( despesa.unidade_orcamentaria ).to eq @iniciativa.unidade_orcamentaria
				end
			end
		end
	end

	describe "#despesas_correntes" do
		context "quando o seu ppa está configurado para detalhar as despesas nas iniciativas" do
			it "retorna a projeção de despesa do tipo 'Despesa Corrente'" do
				allow_any_instance_of(Ppa::Ppa).to receive(:detalha_despesas_nas_iniciativas?).and_return( true )
				iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental)
				iniciativa.despesas_correntes.each do |despesa|
					expect(
						despesa.tipo_de_despesa
					).to eq(Base::TipoDeDespesa.despesa_corrente)
				end
			end
		end

		context "quando seu ppa não está configurado para detalhar despesas nas iniciativas" do
			it "retorna nil" do
				iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental)
				expect(iniciativa.despesas_correntes).to be_empty
			end
		end
	end

	describe "#despesas_de_capital" do
		context "quando o seu ppa está configurado para detalhar as despesas nas iniciativas" do
			it "retorna a projeção de despesa do tipo 'Despesa de Capital'" do
				allow_any_instance_of(Ppa::Ppa).to receive(:detalha_despesas_nas_iniciativas?).and_return( true )
				iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental)
				iniciativa.despesas_de_capital.each do |despesa|
					expect(
						despesa.tipo_de_despesa
					).to eq(Base::TipoDeDespesa.despesa_de_capital)
				end
			end
		end

		context "quando seu ppa não está configurado para detalhar despesas nas iniciativas" do
			it "retorna nil" do
				iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental)
				expect(iniciativa.despesas_de_capital).to be_empty
			end
		end
	end

	describe "ao atualizar a unidade orçamentária de uma iniciativa" do
		cria_tipos_de_despesa
		context "com o ppa configurado para detalhar as despesas no programa" do
			context "com o programa tendo outras iniciativas para a unidade orçamentária antiga" do
				it "não altera nada" do
					iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )
					outra_iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental,
						codigo: '002',
						descricao: 'outra iniciativa'
					)

					iniciativa.unidade_orcamentaria = FactoryBot.create(:unidade_orcamentaria_gabinete_do_secretario)
					programa = iniciativa.programa
					expect{
						iniciativa.save
					}.to_not change{
						programa.projecoes_de_despesa.where(
							unidade_orcamentaria_id: outra_iniciativa.unidade_orcamentaria_id
						).count
					}
				end
			end

			context "com o programa sem outras iniciativas para a unidade orçamentária antiga" do
				it "apaga as projeções de despesa daquele programa para a unidade orçamentária antiga" do
					iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )
					iniciativa.ppa.configuracoes.detalhar_despesas_nas_iniciativas = true
					unidade_orcamentaria_antiga = iniciativa.unidade_orcamentaria

					iniciativa.update(unidade_orcamentaria: Ppa::UnidadeOrcamentaria.find_or_create_by!(FactoryBot.attributes_for(:unidade_orcamentaria_gabinete_do_secretario)))
					expect( iniciativa.programa.projecoes_de_despesa.where(unidade_orcamentaria_id: unidade_orcamentaria_antiga.id).count ).to eq 0
				end
			end

			context "com o programa tendo outras iniciativas para a unidade orçamentária nova" do
				it "não altera nada" do
					iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )
					outra_iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental,
						codigo: '002',
						descricao: 'outra iniciativa',
						unidade_orcamentaria: FactoryBot.create(:unidade_orcamentaria_gabinete_do_secretario)
					)

					iniciativa.unidade_orcamentaria = outra_iniciativa.unidade_orcamentaria
					programa = iniciativa.programa
					expect{
						iniciativa.save
					}.to_not change{
						programa.projecoes_de_despesa.where(
							unidade_orcamentaria_id: iniciativa.unidade_orcamentaria_id
						).count
					}
				end
			end

			context "com o programa sem outras iniciativas para a unidade orçamentária nova" do
				it "cria as projeções de despesa para o programa com a nova unidade orçamentária" do
					iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )

					iniciativa.unidade_orcamentaria = FactoryBot.create(:unidade_orcamentaria_gabinete_do_secretario)
					programa = iniciativa.programa
					expect{
						iniciativa.save
					}.to change{
						programa.projecoes_de_despesa.where(
							unidade_orcamentaria_id: iniciativa.unidade_orcamentaria_id
						).count
					}.by( 8 )
				end
			end
		end
	end

	describe "#after_update" do
		cria_tipos_de_despesa
		context "quando o ppa detalha as despesas nas iniciativas" do
			it "atualiza a unidade orçamentária das suas projeções de despesas" do
				allow_any_instance_of( Ppa::Ppa ).to receive( :detalha_despesas_nas_iniciativas? ).and_return( true )
				iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )

				nova_unidade_orcamentaria = FactoryBot.create(:unidade_orcamentaria_gabinete_do_secretario)
				iniciativa.unidade_orcamentaria = nova_unidade_orcamentaria
				iniciativa.save

				iniciativa.projecoes_de_despesa.each do |projecao_de_despesa|
					expect( projecao_de_despesa.unidade_orcamentaria_id ).to eq nova_unidade_orcamentaria.id
				end
			end
		end
	end

	describe "#after_destroy" do
		cria_tipos_de_despesa

		context "quando há outras iniciativas no programa com a mesma unidade orçamentária" do
			it "não apaga nada" do
				iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )
				outra_iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental,
					codigo: '002',
					descricao: 'outra iniciativa'
				)
				programa = iniciativa.programa

				expect{
					iniciativa.destroy
				}.to_not change{
					programa.projecoes_de_despesa.where(
						unidade_orcamentaria_id: outra_iniciativa.unidade_orcamentaria_id
					).count
				}
			end
		end

		context "quando não há outras iniciativas no programa com a mesma unidade orçamentária" do
			it "apaga as projeções de despesas daquele programa para aquela unidade orçamentária" do
				iniciativa = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental )

				programa = iniciativa.programa
				expect{
					iniciativa.destroy
				}.to change{
					programa.projecoes_de_despesa.where(
						unidade_orcamentaria_id: iniciativa.unidade_orcamentaria_id
					).count
				}.by( -8 )
			end
		end
	end
	describe "#ppa" do
		cria_tipos_de_despesa
		it "retorna o ppa através do objetivo e programa" do
			iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental)
			expect( iniciativa.ppa ).to eq iniciativa.objetivo.programa.ppa
		end
	end

	describe "#programa" do
		cria_tipos_de_despesa
		it "retorna o programa através do objetivo" do
			iniciativa = FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental)
			expect( iniciativa.programa ).to eq iniciativa.objetivo.programa
		end
	end

	describe ".sugestao_de_codigo_para_a_proxima_iniciativa" do
		cria_tipos_de_despesa
		before do
			@projeto = FactoryBot.create(:projeto)
			@atividade = FactoryBot.create(:atividade)
			@operacoes_especiais = FactoryBot.create(:operacoes_especiais)

			iniciativa_0001 = FactoryBot.create( :ppa_iniciativa_para_ensino_fundamental, codigo: '001' )
			FactoryBot.create(:ppa_iniciativa_para_ensino_fundamental, codigo: '002', natureza_da_iniciativa_id: @projeto.id, descricao: 'outra iniciativa')

			outro_objetivo = FactoryBot.create(
				:ppa_objetivo,
				orgao_id:  FactoryBot.create( :orgao_secretaria_da_fazenda, :ppa_2010 ).id,
				programa_id: FactoryBot.create( :programa_escolar, :ppa_2010 ).id
			)
			unidade_orcamentaria_de_outro_ppa = FactoryBot.create( :unidade_orcamentaria_administracao, orgao: outro_objetivo.orgao )

			outra_iniciativa_0002 = FactoryBot.create(
				:ppa_iniciativa_para_ensino_fundamental,
				codigo: '002',
				objetivo_id: outro_objetivo.id,
				unidade_orcamentaria_id: unidade_orcamentaria_de_outro_ppa.id
			)

			outra_iniciativa_2002 = FactoryBot.create(
				:ppa_iniciativa_para_ensino_fundamental,
				codigo: '002',
				descricao: 'outra iniciativa',
				natureza_da_iniciativa_id: @atividade.id,
				objetivo_id: outro_objetivo.id,
				unidade_orcamentaria_id: unidade_orcamentaria_de_outro_ppa.id
			)

			@ppa_2014 = iniciativa_0001.ppa
			@ppa_2010 = outra_iniciativa_0002.ppa
		end

		it "busca a sugestão de código para a próxima iniciativa" do
			expect(
				Ppa::Iniciativa.sugestao_de_codigo_para_a_proxima_iniciativa( @ppa_2014.id, @operacoes_especiais.id )
			).to eq 2

			expect(
				Ppa::Iniciativa.sugestao_de_codigo_para_a_proxima_iniciativa( @ppa_2014.id, @projeto.id )
			).to eq 3

			expect(
				Ppa::Iniciativa.sugestao_de_codigo_para_a_proxima_iniciativa( @ppa_2014.id, @atividade.id )
			).to eq 1

			expect(
				Ppa::Iniciativa.sugestao_de_codigo_para_a_proxima_iniciativa( @ppa_2010.id, @operacoes_especiais.id )
			).to eq 3

			expect(
				Ppa::Iniciativa.sugestao_de_codigo_para_a_proxima_iniciativa( @ppa_2010.id, @projeto.id )
			).to eq 1

			expect(
				Ppa::Iniciativa.sugestao_de_codigo_para_a_proxima_iniciativa( @ppa_2010.id, @atividade.id )
			).to eq 3
		end
	end
end
