class Contabilidade::MedicaoDaObra < ApplicationRecord
	has_paper_trail
	include AASM
	include TradutorConcern
	include GeradorDeEventosContabeis

	attr_accessor :empenhos_da_medicao_da_obra_hidden
	attr_accessor :modulo_atual

	belongs_to :obra, required: true
	belongs_to :engenheiro_responsavel, class_name: 'Base::Pessoa' #não está sendo usado
	belongs_to :responsavel_da_empresa, class_name: 'Base::Pessoa'
	belongs_to :responsavel_da_prefeitura, class_name: 'Base::Pessoa'

	has_many :empenhos_da_medicao_da_obra, class_name: 'Obra::EmpenhoDaMedicaoDaObra', dependent: :destroy
	has_many :empenhos, through: :empenhos_da_medicao_da_obra, class_name: 'Contabilidade::Empenho'
	has_many :sub_elementos_de_despesa, through: :empenhos, class_name: 'Contabilidade::SubElementoDeDespesa'
	has_many :pessoas, through: :empenhos, class_name: 'Base::Pessoa'
	has_many :tipo_de_pessoas, through: :pessoas, class_name: 'Base::TipoDePessoa'
	has_many :itens_do_servico_da_medicao_da_obra, class_name: 'Obra::ItemDoServicoDaMedicaoDaObra', dependent: :destroy
	has_many :itens_do_servico_da_obra, through: :itens_do_servico_da_medicao_da_obra, class_name: 'Obra::ItemDoServicoDaObra'
	has_many :medicoes_da_caixa_da_obra, class_name: 'Obra::MedicaoDaCaixaDaObra', dependent: :destroy
	has_many :documentos_da_medicao_da_obra, class_name: 'Obra::DocumentoDaMedicaoDaObra', dependent: :destroy
	has_many :servicos_da_obra, through: :obra, class_name: 'Obra::ServicoDaObra'

	has_many :liquidacoes, class_name: "Contabilidade::Liquidacao"
	has_many :solicitacoes_por_email, class_name: 'Obra::SolicitacaoPorEmail', dependent: :destroy

	has_many :fiscalizacoes, class_name: "Obra::Fiscalizacao"

	accepts_nested_attributes_for :empenhos_da_medicao_da_obra, reject_if: :all_blank, allow_destroy: true
	accepts_nested_attributes_for :itens_do_servico_da_medicao_da_obra, reject_if: :all_blank, allow_destroy: true

	validates :numero, :valor, :data_inicial, :data_final, :responsavel_da_empresa_id, :responsavel_da_prefeitura_id, :art, presence: true

	validates_uniqueness_of :numero, scope: :obra_id, case_sensitive: false
	validates_numericality_of :numero, greater_than: 0

	validates :empenhos_da_medicao_da_obra, uniq_nested_attributes: { atributo: :empenho_id, mensagem: "empenho deve ser único dentro de uma medição da obra" }
	validates :itens_do_servico_da_medicao_da_obra, uniq_nested_attributes: { atributo: :item_do_servico_da_obra_id, mensagem: "item do servico deve ser único dentro de uma medição da obra" }

	validates_length_of :art, maximum: 20

	validate :valor_maior_que_o_saldo?
	validate :data_nao_pode_ser_anterior_a_data_de_inicio_da_obra
	validate :data_final_nao_pode_ser_anterior_a_data_inicial
	validate :valor_dos_itens_do_servico_deve_bater_com_valor_da_medicao
	validates :data_inicial, :data_final, date: true

	after_validation :valor_da_medicao_diferente_do_valor_dos_empenhos, if: Proc.new { self.empenhos_da_medicao_da_obra.reject(&:marked_for_destruction?).any? }

	scope :contabilizadas, -> { where(status: [:confirmada, :lancada]) }

	enum status: {
		aberta: 0,
		enviada_a_cef: 1,
		liberada_pela_cef: 2,
		confirmada: 3,
		lancada: 4
	}

	aasm column: :status, enum: true, whiny_transitions: false do
		state :aberta, :initial => true
		state :enviada_a_cef
		state :liberada_pela_cef
		state :confirmada
		state :lancada

		event :enviar_para_cef do
			transitions from: :aberta, to: :enviada_a_cef do
				guard do
					self.aberta? && self.obra.origem_cef?
				end
			end
		end

		event :liberar_pela_cef do
			transitions from: :enviada_a_cef, to: :liberada_pela_cef do
				guard do
					self.enviada_a_cef? && self.obra.origem_cef?
				end
			end
		end

		event :confirmar do
			transitions from: :liberada_pela_cef, to: :confirmada do
				guard do
					self.liberada_pela_cef? && medicoes_da_caixa_da_obra.any? && self.obra.origem_cef?
				end
			end
			transitions from: :aberta, to: :confirmada do
				guard do
					!self.obra.origem_cef?
				end
			end
		end

		event :voltar_para_aberta do
			transitions from: [:enviada_a_cef, :liberada_pela_cef, :confirmada, :lancada], to: :aberta do
				guard do
					!self.obra.origem_cef?
				end
				after do
					if self.medicoes_da_caixa_da_obra.any?
						self.medicoes_da_caixa_da_obra.destroy_all
					end
				end
			end
			transitions from: :confirmada, to: :aberta do
				guard do
					!self.medicoes_da_caixa_da_obra.any? && !foi_liquidada?
				end
			end
			transitions from: :lancada, to: :aberta 
			after do
				exclui_movimentacoes_do_plano_de_contas
			end
		end

		event :voltar_para_confirmado do
			transitions from: :lancada, to: :confirmada
		end

		event :lancar do
			transitions from: :confirmada, to: :lancada
		end
	end

	def descricao_da_medicao
		if self.responsavel_da_empresa.present?
			"Medição: #{numero} / Realizada por: #{responsavel_da_empresa.nome_e_cpf_ou_cnpj} / Obra: #{obra.codigo}"
		else
			"Medição: #{numero} / Obra: #{obra.codigo}"
		end
	end

	def descricao_da_medicao_com_periodo
		"Medição: #{numero} / #{self.periodo_da_medicao} / Art: #{art}"
	end

	def periodo_da_medicao
		"Período - #{self.data_inicial} a #{self.data_final}"
	end

	def valor_total_liquidado
		liquidacoes.where('estornada <> true').map { |liq| liq if liq.empenho.empenho_de_restos_a_pagar.present? == false || liq.empenho.restos_a_pagar == true }.sum(&:valor).to_f
	end

	def saldo_da_medicao_a_liquidar
		valor.to_f - valor_total_liquidado
	end


	def valor_maior_que_o_saldo?
		if obra.present?
			if self.persisted?
				valor_no_banco_de_dados = Contabilidade::MedicaoDaObra.find(self.id).valor
				errors.add(:valor, "não pode ser maior que o saldo atual") if (self.obra.saldo_para_medicoes.to_f + valor_no_banco_de_dados.to_f) < self.valor.to_f
			else
				errors.add(:valor, 'não pode ser maior que o saldo atual') if self.obra.saldo_para_medicoes.to_f < self.valor.to_f
			end
		end
	end

	def valor_dos_itens_do_servico_deve_bater_com_valor_da_medicao
		return unless itens_do_servico_da_medicao_da_obra.any? && (self.valor.to_f != valor_total_dos_itens_do_servico_da_medicao_da_obra)
		errors.add(:base, 'A soma dos itens do Serviço da Medição deve ser igual ao valor da Medição.')
	end

	def data_final_nao_pode_ser_anterior_a_data_inicial
		unless data_inicial.nil? || data_final.nil?
			errors.add(:data_final, "deve ser maior que a data inicial") if data_final < data_inicial
		end
	end

	def data_nao_pode_ser_anterior_a_data_de_inicio_da_obra
		if obra.present? && !data_inicial.nil?
			errors.add(:data_inicial, "deve ser a partir do inicio da obra #{obra.data_de_inicio}") if data_inicial < obra.data_de_inicio
		end
	end

	def valor_da_medicao_diferente_do_valor_dos_empenhos
		errors.add(:valor, 'Valor da medição e soma dos empenhos devem ser iguais.') if self.valor.to_d != valor_total_dos_empenhos_da_medicao_da_obra
	end

	def foi_liquidada?
		return liquidacoes.any? &&  valor_total_liquidado.to_f >= self.valor.to_f
	end

	def convidar
		if obra.servicos_da_obra.any? && self.obra.contrato.pessoa.email.present?
			data_expiracao = Date.today + Configuracao.last.tempo_limite_da_medicao
			gerar_codigo_de_acesso(data_expiracao)
			@emails = [self.obra.contrato.pessoa.email, self.obra.contrato.pessoa.email_alternativo]
			@emails.each do |email|
				unless email.blank?
					PrincipalMailer.solicitar_medicao_da_obra( self.id, email ).deliver_later unless Rails.env.development? || Rails.env.test?
					solicitacoes_por_email.create(data_de_envio: DateTime.now, email: email, data_de_expiracao: data_expiracao, codigo_de_acesso: self.codigo_de_acesso)
				end
			end
		end
	end

	def medicao_por_email_respondida?
		solicitacoes_por_email.where(respondido: true).any?
	end

	def atualiza_solicitacao_de_email
		solicitacoes_por_email.find_by(codigo_de_acesso: self.codigo_de_acesso).update_columns(respondido: true, data_da_medicao: DateTime.now)
	end

	def saldo_a_liquidar
		self.valor.to_d - valor_total_liquidado.to_d
	end

	def valor_total_dos_empenhos_da_medicao_da_obra
		liquidacoes
		self.empenhos_da_medicao_da_obra.reject(&:marked_for_destruction?).sum(&:valor)
	end

	def porcentagem
		if self.valor.present?
			porcentagem = (( self.valor * 100).to_f / (self.obra.contrato.valor_total_do_contrato).to_f).round(2)
			porcentagem.cap_at 100
		end
	end

	def valor_total_dos_itens_do_servico_da_medicao_da_obra
		itens_do_servico_da_medicao_da_obra.inject(0) { |total, item_do_servico_da_medicao_da_obra| total. + item_do_servico_da_medicao_da_obra.preco_total_medido.to_f.round(2) }
	end

	def retorna_periodo_total_para_liberacao
		total = medicoes_da_caixa_da_obra.inject(0) { |total, medicao_da_caixa_da_obra|
			total + medicao_da_caixa_da_obra.retorna_periodo_da_liberacao
		}
	end

	def total_bdi
		servicos_da_obra.sum(:valor_bdi)
	end

	def total_geral_com_bdi
		self.valor + self.total_bdi
	end

	def eventos_contabeis_a_serem_lancados_no_reconhecimento
		self.modulo_atual = "contabilidade"
		status_de_lancamento = 4
		status = Contabilidade::ConfiguracaoDoEventoContabil.status["lancada"] rescue nil
		modulo_de_ativacao = Contabilidade::ConfiguracaoDoEventoContabil.modulo_de_ativacoes[self.modulo_atual.to_sym]

		modalidades_do_empenho = self.empenhos.pluck(:modalidade).uniq
		if (modalidades_do_empenho & ["global", "estimativo"] ).any?
			modalidades_do_empenho.push("global_ou_estimativo")
		end
		modalidades_do_empenho = modalidades_do_empenho.map { |modalidade| Contabilidade::ConfiguracaoDoEventoContabil.modalidades_do_empenho[modalidade] }

		tipo_de_obra = Contabilidade::ConfiguracaoDoEventoContabil.tipos_de_obra[self.obra.try(:tipo_de_obra)] rescue nil 

		eventos_inclusivos_de_dotacao_e_sem_dotacao = Contabilidade::EventoContabil.left_outer_joins( configuracao_do_evento_contabil: [:sub_elementos_de_despesa_da_configuracao_contabil ] ).where(
			contabilidade_eventos_contabeis: {
				modelo: Contabilidade::EventoContabil.modelos[:medicao_da_obra],
				orcamento_id: self.empenhos.pluck(:orcamento_id).uniq #Precisa pegar todos os empenhos pois pode existr medições com empenhos de Orçamentos diferentes (Restos a Pagars)
			},
			contabilidade_configuracoes_do_evento_contabil: {
				status: [status, nil],
				modulo_de_ativacao: [modulo_de_ativacao, nil],
				tipo_de_evento: [Contabilidade::ConfiguracaoDoEventoContabil.tipos_de_evento[:inclusivo_de_dotacoes], nil],
				classificacao_da_obra: [self.obra.try(:classificacao_do_bem).try(:codigo), nil],
				modalidade_do_empenho: [modalidades_do_empenho, nil],
				tipo_de_obra: [tipo_de_obra, nil],
				tipo_de_pessoa: [tipos_de_pessoa_por_model ,nil],
				resto_a_pagar: [empenhos.any?(&:restos_a_pagar), nil]
			},
			contabilidade_sub_elementos_de_despesa_da_configuracao_contabil: {
				sub_elemento_de_despesa_id: [self.sub_elementos_de_despesa.ids , nil]
			}
		)

		eventos_exclusivos_de_dotacao = Contabilidade::EventoContabil.joins( configuracao_do_evento_contabil: [:sub_elementos_de_despesa_da_configuracao_contabil ] ).where(
			contabilidade_eventos_contabeis: {
				modelo: Contabilidade::EventoContabil.modelos[:medicao_da_obra],
				orcamento_id: self.empenhos.pluck(:orcamento_id).uniq #Precisa pegar todos os empenhos pois pode existr medições com empenhos de Orçamentos diferentes (Restos a Pagars)
			},
			contabilidade_configuracoes_do_evento_contabil: {
				status: [status, nil],
				modulo_de_ativacao: [modulo_de_ativacao, nil],
				tipo_de_evento: [Contabilidade::ConfiguracaoDoEventoContabil.tipos_de_evento[:exclusivo_de_dotacoes], nil],
				classificacao_da_obra: [self.obra.try(:classificacao_do_bem).try(:codigo), nil],
				modalidade_do_empenho: [modalidades_do_empenho, nil],
				tipo_de_obra: [tipo_de_obra, nil],
				tipo_de_pessoa: [tipos_de_pessoa_por_model ,nil],
				resto_a_pagar: [empenhos.any?(&:restos_a_pagar), nil]
			}
		).where.not(
			contabilidade_sub_elementos_de_despesa_da_configuracao_contabil: {
				sub_elemento_de_despesa_id: [self.sub_elementos_de_despesa.ids]
			}
		)

		return (eventos_inclusivos_de_dotacao_e_sem_dotacao + eventos_exclusivos_de_dotacao).uniq
	end

	def exclui_movimentacoes_do_plano_de_contas
		if self.movimentacoes_do_plano_de_contas.any?
			self.movimentacoes_do_plano_de_contas.destroy_all
		end
	end

	private
	def gerar_codigo_de_acesso data_de_expiracao
		codigo_de_acesso = SecureRandom.base64(16).gsub("/","_").gsub(/=+$/,"")
		update_columns(codigo_de_acesso: codigo_de_acesso, validade_codigo_de_acesso: data_de_expiracao)
	end
end
