# frozen_string_literal: true

require_relative 'command_output_processor'
require_relative 'process'
require_relative 'ruby_spawn'
require 'high_water_mark/threshold'

module Quality
  # Runs a quality-checking, command, checks it agaist the existing
  # number of violations for that command, and decreases that number
  # if possible, or outputs data if the number of violations increased.
  class QualityChecker
    def initialize(cmd, command_options, output_dir, verbose,
                   minimum_threshold,
                   logger: STDOUT,
                   count_file: File,
                   count_io: IO,
                   command_output_processor_class:
                     Quality::CommandOutputProcessor,
                   count_dir: Dir,
                   process_class: Process)
      @minimum_threshold = minimum_threshold
      @count_file = count_file
      @count_io = count_io
      @command_output_processor_class = command_output_processor_class
      @logger = logger
      @count_dir = count_dir
      @cmd = cmd
      @command_options = command_options
      @verbose = verbose
      @count_dir.mkdir(output_dir) unless @count_file.exists?(output_dir)
      @threshold = HighWaterMark::Threshold.new(cmd,
                                                count_io: count_io,
                                                count_file: count_file,
                                                output_dir: output_dir)
      @process_class = process_class
    end

    def execute(&count_violations_on_line)
      processor, exit_status = process_command(&count_violations_on_line)
      @violations = processor.violations
      check_exit_status(exit_status)
      ratchet_violations
    end

    private

    def process_command(&count_violations_on_line)
      processor = @command_output_processor_class.new
      processor.emacs_format = @command_options[:emacs_format]
      exit_status = run_command(processor, &count_violations_on_line)
      [processor, exit_status]
    end

    def run_command(processor, &count_violations_on_line)
      runner = @process_class.new(full_cmd + ' 2>&1')

      puts rendered_full_cmd if @verbose
      runner.run do |file|
        processor.file = file
        @command_output = processor.process(&count_violations_on_line)
      end
    end

    def check_exit_status(exit_status)
      return if @command_options[:gives_error_code_on_violations] ||
                @command_options[:gives_error_code_on_no_relevant_code]
      return unless exit_status.nonzero?

      raise("Error detected running #{rendered_full_cmd}.  " \
            "Exit status is #{exit_status}.  Output was #{@command_output}")
    end

    MAX_VIOLATIONS = 9_999_999_999

    def existing_violations
      @existing_violations ||= (@threshold.threshold || MAX_VIOLATIONS)
    end

    def rendered_full_cmd
      full_cmd.scan(/.{1,78}/).join("\\\n")
    end

    def error_too_many_violations!
      raise("Output from:\n\n" \
            "#{rendered_full_cmd}\n\n#{@command_output}\n\n" \
            "Reduce total number of #{@cmd} violations " \
            "to #{existing_violations} or below!")
    end

    def violations_to_write
      @violations_to_write ||= [@violations, @minimum_threshold].max
    end

    def report_ratchet
      if @violations < existing_violations &&
         existing_violations != MAX_VIOLATIONS
        @logger.puts 'Ratcheting quality up...'
      end
    end

    def ratchet_violations
      report_violations(existing_violations)
      if @violations > [existing_violations, @minimum_threshold].max
        error_too_many_violations!
      elsif violations_to_write != existing_violations
        report_ratchet
        write_violations(violations_to_write)
      end
    end

    def report_violations(existing)
      @logger.puts "Existing violations: #{existing}" if existing != MAX_VIOLATIONS
      @logger.puts "Found #{@violations} #{@cmd} violations"
    end

    def full_cmd
      args = @command_options[:args] || ''
      @found_output = false
      RubySpawn.new(@cmd, args).invocation
    end

    def write_violations(new_violations)
      @threshold.write_violations(new_violations)
    end
  end
end
