require 'json'
require 'fileutils'
require 'open3'
require 'arli'
require 'arli/commands/base'
require 'arli/errors'
require 'arli/library/multi_version'
require 'arduino/library'

module Arli
  module Commands
    class Search < Base
      require 'arduino/library/include'

      attr_accessor :search_string,
                    :search_opts,
                    :search_method,
                    :results,
                    :limit,
                    :database,
                    :format,
                    :unique_libraries

      def initialize(*args)
        super(*args)
        self.format   = config.search.results.output_format
        valid_methods = Arli::Library::MultiVersion.format_methods
        unless valid_methods.include?(format)
          raise Arli::Errors::InvalidSearchSyntaxError,
                "invalid format #{format}"
        end
      end

      def additional_info
        "\nSearching For: #{runtime.argv.join(' ').bold.green}\n"
      end

      def run
        self.search_opts      = process_search_options!
        self.results          = search(database, **search_opts).sort
        self.unique_libraries = Set.new

        results.map { |lib| add_lib_or_version(lib) }

        unique_libraries.each do |multi_version|
          puts multi_version.send("to_s_#{format}".to_sym)
        end
        print_total_with_help
      rescue StandardError => e
        error e
        puts e.backtrace.join("\n") if ENV['DEBUG']
      end

      def add_lib_or_version(lib)
        a_version = Arli::Library::MultiVersion.new(lib)
        if unique_libraries.include?(a_version)
          unique_libraries.find { |l| l.name == a_version.name }&.add_version(library: lib)
        else
          unique_libraries << a_version
        end
      end

      def process_search_options!
        self.search_string = extract_search_argument!
        raise_error_unless_search_string!

        self.limit  = config.search.results.limit
        search_opts = {}
        begin
          params_code = "{ #{search_string} }"
          puts "Evaluating: [#{params_code.blue}]\nSearch Method: [#{search_method.to_s.green}]" if config.trace
          search_opts = eval(params_code)
        rescue StandardError => e
          handle_and_raise_error(e)
        end

        unless search_opts.is_a?(::Hash) && !search_opts.empty?
          raise Arli::Errors::InvalidSearchSyntaxError,
                "Search string '#{search_string}' did not eval to Hash.\n"
        end

        self.database = db_default

        search_opts.merge!(limit: limit) if limit && limit > 0
        search_opts.delete(:limit) if limit == 0
        search_opts
      end

      def extract_search_argument!
        search = runtime.argv.first
        if search =~ /:/
          self.search_method = :ruby
          search
        elsif search.start_with?('/')
          self.search_method = :regex_name_and_url
          # exact match
          "#{config.search.default_field}: #{search}"
        elsif search.start_with?('=')
          self.search_method = :equals
          # exact match
          "#{config.search.default_field}: '#{search[1..-1]}'"
        elsif search
          self.search_method = :regex
          "#{config.search.default_field}: /#{search.downcase}/i"
        end
      end

      def raise_error_unless_search_string!
        unless search_string
          raise Arli::Errors::InvalidSyntaxError,
                'Expected an argument or a flag to follow the command ' + 'search'.bold.green
        end
      end

      def handle_and_raise_error(e)
        message = e.message
        if message =~ /undefined method.*Arduino::Library::Model/
          message = 'Invalid attributed search. Possible values are:' \
                    "\n#{Arduino::Library::Types::LIBRARY_PROPERTIES.keys}"
        end
        raise Arli::Errors::InvalidSearchSyntaxError,
              "Search string '#{search_string}' is invalid.\n" +
              message.red
      end

      def print_total_with_help
        hr_short
        puts "  Total Versions : #{results.size.to_s.bold.magenta}\n"
        puts "Unique Libraries : #{unique_libraries.size.to_s.bold.magenta}\n"
        hr_short
        if results.size == Arli::Configuration::DEFAULT_RESULTS_LIMIT
          puts "Hint: use #{'-m 5'.bold.green} to limit the result set."
        end
      end

      def params
        search_opts
      end

      private

      def hr_short
        puts '———————————————————————'
      end
    end
  end
end
