require 'hive'

module Scrape

  # Scrapes all known methods for all known APIs and saves them to as `.yml`.
  class ApiDefinitionsJob
    attr_accessor :url, :api_data_path, :jsonrpc

    DEFAULT_URL = 'https://appbasetest.timcliff.com'
    DEFAULT_API_DATA_PATH = '_data/apidefinitions'

    def initialize(options = {url: DEFAULT_URL, api_data_path: DEFAULT_API_DATA_PATH})
      @url = options[:url] || DEFAULT_URL
      @api_data_path = options[:api_data_path] || DEFAULT_API_DATA_PATH
      @jsonrpc = Hive::Jsonrpc.new(url: @url)
    end

    # Execute the job.
    #
    # @return [Integer] total number of methods added or changed in this pass
    def perform
      method_change_count = 0
      all_signatures = jsonrpc.get_all_signatures

      apis.each do |api, methods|
        signatures = all_signatures.select { |k, v| k == api }
        file_name = "#{api_data_path}/#{api}.yml"
        api_method_change_count = 0
        puts "Definitions for: #{api}, methods: #{methods.size}"

        dirty, yml = if File.exist?(file_name)
                       [false, YAML.load_file(file_name)]
                     else
                       # Initialize
                       [true, [
                           'name' => api,
                           'description' => nil,
                           'methods' => []
                       ]]
                     end

        signatures[api].each do |method, signature|
          method_name = "#{api}.#{method}"
          existing_api_method = yml[0]['methods'].reverse.find{|e| e['api_method'] == method_name}

          parameter_json = signature.args.to_json
          expected_response_json = signature.ret.to_json

          if existing_api_method

            case api
            when :condenser_api
              # skipping signature analysis on condenser_api
              # puts "A: " + existing_api_method['parameter_json'].inspect
              # puts "B: " + parameter_json.inspect
              # puts "C: " + existing_api_method['expected_response_json'].inspect
              # puts "D: " + expected_response_json.inspect
              next
            else
              if existing_api_method['parameter_json'] == parameter_json &&
                  existing_api_method['expected_response_json'] == expected_response_json
                next
              else
                existing_api_method['parameter_json'] = parameter_json
                existing_api_method['expected_response_json'] = expected_response_json
                puts "\tChanged: #{method}"
              end
            end
          else
            puts "\tAdding: #{method}"
          end

          dirty = true
          unless existing_api_method
            yml[0]['methods'] << {
                'api_method' => method_name,
                'purpose' => nil,
                'parameter_json' => parameter_json,
                'expected_response_json' => expected_response_json
            }
          end

          api_method_change_count += 1
          method_change_count += 1
        end

        yml[0]['methods'].each do |method|
          existing_api, existing_method = method['api_method'].split('.')

          unless methods.include?(existing_method.to_sym)
            puts "\tDropped method: #{existing_method} (recommend removal from #{file_name})"
          end
        end

        next unless dirty

        File.open(file_name, 'w+') do |f|
          f.write("# Generated by #{self.class.name.to_s}: #{Time.now.utc}\n")
          f.write("# See: https://git.io/vx5CY\n")
          f.write yml.to_yaml
        end

        puts "\t#{api_method_change_count} methods added/updated in #{api}"
      end

      method_change_count
    end
    private
    def apis
      apis = {}

      jsonrpc.get_methods do |methods|
        methods.each do |method|
          api, method = method.split('.').map(&:to_sym)

          apis[api] ||= []
          apis[api] << method
        end
      end

      apis
    end
  end
end
