# frozen_string_literal: true

require 'rails_helper'
require_relative '../support/contexts/process_template'

module RailsWorkflow
  RSpec.describe ErrorResolver do
    include_context 'process template'

    let(:template) { prepare_template }
    let(:process_manager) do
      ProcessManager.new(
        template_id: template.id, context: { msg: 'Test' }
      )
    end

    let(:process) { process_manager.create_process }
    let(:process_runner) { RailsWorkflow::ProcessRunner.new(process) }

    context 'operation fails' do
      let(:operation) { process.operations.first }
      let(:error) { operation.workflow_errors.first }
      let(:error_resolver) { described_class.new(error) }

      context 'to start' do
        before do
          with_failing_instance(Operation, :execute) do
            process_runner.start
          end
        end

        it 'set proper process status after retry' do
          expect { error_resolver.retry }
            .to change { process.reload.status }
            .from(Status::ERROR).to(Status::DONE)
        end

        it 'set proper operation status after retry' do
          expect { error_resolver.retry }
            .to change { process.reload.operations.first.status }
            .from(Status::ERROR).to(Status::DONE)
        end
      end

      context 'to wait' do
        before do
          allow_any_instance_of(Operation)
            .to receive(:can_start?).and_return(false)

          with_failing_instance(Operation, :update_attribute) do
            process_runner.start
          end
        end

        it 'set proper process status' do
          expect { error_resolver.retry }
            .to change { process.reload.status }
            .from(Status::ERROR).to(Status::IN_PROGRESS)
        end

        it 'set proper operation status' do
          expect { error_resolver.retry }
            .to change { process.reload.operations.first.status }
            .from(Status::ERROR).to(Status::WAITING)
        end
      end

      context 'execution fails' do
        before do
          with_failing_instance(Operation, :execute) do
            process_runner.start
          end
        end

        it 'set proper process status' do
          expect { error_resolver.retry }
            .to change { process.reload.status }
            .from(Status::ERROR).to(Status::DONE)
        end

        it 'set proper operation status' do
          expect { error_resolver.retry }
            .to change { process.reload.operations.first.status }
            .from(Status::ERROR).to(Status::DONE)
        end
      end
    end

    context 'operation building fails' do
      let(:operation) { process.operations.first }
      let(:error) { process.workflow_errors.first }
      let(:error_resolver) { described_class.new(error) }

      context 'when operation build fails' do
        before do
          with_failing(Operation, :create) do
            process_runner.start
          end
        end

        it 'set proper process status' do
          expect { error_resolver.retry }
            .to change { process.reload.status }
            .from(Status::ERROR).to(Status::DONE)
        end

        it 'set proper operation status' do
          expect { error_resolver.retry }
            .to change { process.reload.operations&.first&.status }
            .to(Status::DONE)
        end
      end
    end

    context 'process runner fails to start' do
      let(:error) { Error.first }
      let(:error_resolver) { described_class.new(error) }

      context 'when operation build fails' do
        before do
          with_failing_instance(ProcessRunner, :start) do
            process_manager.create_process
            process_manager.start_process
          end
        end

        it 'set proper process status' do
          expect { error_resolver.retry }
            .to change { Process.first.status }
            .from(Status::ERROR).to(Status::DONE)
        end

        it 'creates and completes operations' do
          expect { error_resolver.retry }
            .to change { Process.first.operations.count }
            .from(1).to(2)
        end
      end
    end

    context 'dependency resolver' do
      let(:operation) { process.operations.first }
      let(:error) { RailsWorkflow::Error.first }
      let(:error_resolver) { described_class.new(error) }

      context 'when operation build fails' do
        before do
          with_failing_instance(DependencyResolver, :matched_templates) do
            process_runner.start
          end
        end

        it 'set proper process status' do
          expect { error_resolver.retry }
            .to change { Process.first.status }
            .from(Status::ERROR).to(Status::DONE)
        end

        it 'set proper operation status' do
          expect { error_resolver.retry }
            .to change { Process.first.operations.count }
            .from(1).to(2)
        end
      end
    end

    def with_failing_instance(target_class, method)
      allow_any_instance_of(target_class)
        .to receive(method) { raise 'Some error' }

      yield

      allow_any_instance_of(target_class).to receive(method).and_call_original
    end

    def with_failing(target, method)
      allow(target).to receive(method) { raise 'Some error' }
      yield
      allow(target).to receive(method).and_call_original
    end
  end
end
