module Travis::API::V3
  class Service
    DEFAULT_PARAMS = [ "include".freeze, "@type".freeze ]
    private_constant :DEFAULT_PARAMS

    def self.result_type(type = nil)
      @result_type   = type if type
      @result_type ||= parent.result_type if parent and parent.respond_to? :result_type
      raise 'result type not set' unless defined? @result_type
      @result_type
    end

    def self.filter_params(params)
      wanted = self.params
      params.select { |key| wanted.include? key }
    end

    def self.params(*list, prefix: nil)
      @params ||= superclass.respond_to?(:params) ? superclass.params.dup : DEFAULT_PARAMS
      list.each do |entry|
        @params << entry.to_s
        @params << "#{prefix || result_type}.#{entry}" if entry.is_a? Symbol
      end
      @params
    end

    def self.paginate(**options)
      params("limit".freeze, "offset".freeze)
      params("sort_by".freeze) if query_factory.sortable?
      @paginator = Paginator.new(**options)
    end

    def self.paginator
      @paginator ||= nil
    end

    def self.paginate?
      !!@paginator if defined? @paginator
    end

    def self.query_factory
      Queries[result_type]
    end

    attr_accessor :access_control, :params, :request_body

    def initialize(access_control, params, request_body)
      @access_control = access_control
      @params         = params
      @queries        = {}
      @github         = {}
      @request_body   = request_body
    end

    def query(type = result_type)
      @queries[type] ||= Queries[type].new(params, result_type, service: self)
    end

    def github(user = nil)
      @github[user] ||= GitHub.new(user)
    end

    def find(type = result_type, *args)
      not_found(true,  type) unless object = query(type).find(*args)
      not_found(false, type) unless access_control.visible? object
      object
    end

    def check_login_and_find(*args)
      raise LoginRequired unless access_control.full_access_or_logged_in?
      find(*args) or raise NotFound
    end

    def not_found(actually_not_found = false, type = nil)
      type, actually_not_found = actually_not_found, false if actually_not_found.is_a? Symbol
      error = actually_not_found ? EntityMissing : NotFound
      raise(error, type || result_type)
    end

    def run!
      not_implemented
    end

    def result_type
      self.class.result_type
    end

    def result(*args)
      Result.new(access_control, *args)
    end

    def run
      not_found unless result = run!
      result = result(result_type, result) unless result.is_a? Result
      result = paginate(result) if self.class.paginate?
      apply_warnings(result)
      result
    end

    def warnings
      @warnings ||= []
    end

    def warn(*args)
      warnings << args
    end

    def apply_warnings(result)
      warnings.each { |args| result.warn(*args) }
    end

    def paginate(result)
      self.class.paginator.paginate(result,
        limit:          params['limit'.freeze],
        offset:         params['offset'.freeze],
        access_control: access_control)
    end

    def params_for?(prefix)
      return true if params['@type'.freeze] == prefix
      return true if params[prefix].is_a? Hash
      params.keys.any? { |key| key.start_with? "#{prefix}." }
    end

    def accepted(**payload)
      payload[:resource_type] ||= result_type
      result(:accepted, payload, status: 202)
    end

    def not_implemented
      raise NotImplemented
    end
  end
end