# frozen_string_literal: true

require 'spec_helper'

module Quby::Answers::Services
  describe UpdatesAnswers do
    let(:answer) { Quby.answers.create!('simple') }
    let(:updates_answers) { UpdatesAnswers.new answer }

    describe '#update' do
      it 'sets the raw parameters on the answer on error' do
        updates_answers.update("v_2" => "unknown_key", 'unknown_field' => 'value_should_be_retained')
        expect(Quby.answers.reload(answer).raw_params["unknown_field"]).to eq "value_should_be_retained"
      end

      it 'sets answer value for the provided key to the provided value' do
        updates_answers.update("v_1" => "value")
        expect(Quby.answers.reload(answer).attributes["value"]["v_1"]).to eq "value"
      end

      it 'disallows setting attributes that are not questions' do
        updates_answers.update("random_key" => "value")
        expect(Quby.answers.reload(answer).attributes["random_key"]).to_not eq "value"
      end

      context 'subquestion key clash' do
        let(:answer) { Quby.answers.create!('subquestion_key_clash') }
        it 'does not remove filtered values from raw_params' do
          updates_answers.update('v_0' => 'a1', 'v_0_a1' => "value")
          expect(Quby.answers.reload(answer).raw_params["v_0_a1"]).to eq("value")
        end
      end

      context 'if the answer cannot be updated correctly' do
        before do
          answer.send :add_error, double(key: :v_1), :valid_integer, "Oh no, ERROR!"
          # allow_server_side_validation_error(always: true)
        end

        it 'reports the error' do
          expect(Roqua::Support::Errors).to receive(:report) do |error|
            expect(error).to be_a(Quby::ValidationError)
            expect(error.message).to eq('["V 1 Oh no, ERROR!"]')
          end
          updates_answers.update('v_0' => 'value')
        end

        # See comment in UpdatesAnswers about why this is a thing.
        it 'does not report the error if the answer was aborted' do
          expect(Roqua::Support::Errors).not_to receive(:report)
          updates_answers.update('v_0' => 'value', "aborted" => true)
        end
      end

      it 'validates the answer' do
        expect(answer).to receive :validate_answers
        updates_answers.update
      end

      it 'cleans up the answer' do
        expect(answer).to receive :cleanup_input
        updates_answers.update
      end

      it 'sets the started_at, entered_at and observation_time' do
        started_at = Time.new(2014, 2, 4, 5, 6, 7)
        expect(answer).to receive(:mark_completed).with(started_at)
        updates_answers.update("rendered_at" => started_at.to_i.to_s)
      end

      it 'calculates scores' do
        outcome = Quby::Answers::Entities::Outcome.new
        calculations = OutcomeCalculation.new(answer)
        allow(OutcomeCalculation).to receive(:new).and_return(calculations)
        expect(calculations).to receive(:calculate).and_return(outcome)
        updates_answers.update
      end
    end
    describe 'always saving raw_params' do
      it 'saves raw_params if there is an exception during cleanup_input' do
        Timecop.freeze do
          expect(answer).to receive(:cleanup_input).and_raise(ArgumentError)
          expect { updates_answers.update('v_1' => 'a1') }.to raise_exception(ArgumentError)
          expect(answer.raw_params).to eq('v_1' => 'a1', 'could_not_update_at' => Time.now.to_i)
        end
      end
    end

    describe 'using observation_time in calculations' do
      # age calculation in scores requires observation_time to be set, so ensure observation_time is set before calculation
      it 'sets observation_time before assigning OutcomeCalculation.new(answer).calculate' do
        expect(answer).to receive(:mark_completed).ordered
        expect(answer).to receive(:outcome=).ordered
        updates_answers.update('v_1' => 'a1')
      end
    end
  end
end