# frozen_string_literal: true

module Chat
  module ServiceMatchers
    class RunServiceSuccessfully
      attr_reader :result

      def matches?(result)
        @result = result
        result.success?
      end

      def failure_message
        inspector = StepsInspector.new(result)
        "Expected to run the service sucessfully but failed:\n\n#{inspector.inspect}\n\n#{inspector.error}"
      end
    end

    class FailStep
      attr_reader :name, :result

      def initialize(name)
        @name = name
      end

      def matches?(result)
        @result = result
        step_exists? && step_failed? && service_failed?
      end

      def failure_message
        set_unexpected_result
        message =
          if !step_exists?
            "Expected #{type} '#{name}' (key: '#{step}') was not found in the result object."
          elsif !step_failed?
            "Expected #{type} '#{name}' (key: '#{step}') to fail but it succeeded."
          else
            "expected the service to fail but it succeeded."
          end
        error_message_with_inspection(message)
      end

      def failure_message_when_negated
        set_unexpected_result
        message = "Expected #{type} '#{name}' (key: '#{step}') to succeed but it failed."
        error_message_with_inspection(message)
      end

      def description
        "fail a #{type} named '#{name}'"
      end

      private

      def step_exists?
        result[step].present?
      end

      def step_failed?
        result[step].failure?
      end

      def service_failed?
        result.failure?
      end

      def type
        self.class.name.split("::").last.sub("Fail", "").downcase
      end

      def step
        "result.#{type}.#{name}"
      end

      def error_message_with_inspection(message)
        inspector = StepsInspector.new(result)
        "#{message}\n\n#{inspector.inspect}\n\n#{inspector.error}"
      end

      def set_unexpected_result
        return unless result[step]
        result[step]["spec.unexpected_result"] = true
      end
    end

    class FailContract < FailStep
    end

    class FailPolicy < FailStep
    end

    class FailToFindModel < FailStep
      def type
        "model"
      end

      def description
        "fail to find a model named '#{name}'"
      end

      def step_failed?
        super && result[name].blank?
      end
    end

    class FailWithInvalidModel < FailStep
      def type
        "model"
      end

      def description
        "fail to have a valid model named '#{name}'"
      end

      def step_failed?
        super && result[step].invalid
      end
    end

    def fail_a_policy(name)
      FailPolicy.new(name)
    end

    def fail_a_contract(name = "default")
      FailContract.new(name)
    end

    def fail_to_find_a_model(name = "model")
      FailToFindModel.new(name)
    end

    def fail_with_an_invalid_model(name = "model")
      FailWithInvalidModel.new(name)
    end

    def fail_a_step(name = "model")
      FailStep.new(name)
    end

    def run_service_successfully
      RunServiceSuccessfully.new
    end

    def inspect_steps(result)
      inspector = Chat::StepsInspector.new(result)
      puts "Steps:"
      puts inspector.inspect
      puts "\nFirst error:"
      puts inspector.error
    end
  end
end