require 'net/https'
require 'json'

require 'clever/error'
require 'clever/models/contact'
require 'clever/models/district'
require 'clever/models/photo'
require 'clever/models/school'
require 'clever/models/section'
require 'clever/models/student'
require 'clever/models/teacher'

# Public: This is a ruby library for interacting with v1.1 of the Clever (https://getclever.com) API.
#
# Examples
#
#   Clever::API.new.district('4fd43cc56d11340000000005')
#   # => { "data": { "name": "Demo District", "id": "4fd43cc56d11340000000005"} }...
class Clever::API
  attr_accessor :http

  BASE_URL = 'https://api.getclever.com/v1.1'

  def initialize
    @url = URI.parse(BASE_URL)
    @http = Net::HTTP.new(@url.host, @url.port)
    @http.use_ssl = true
    @http.verify_mode = OpenSSL::SSL::VERIFY_PEER
  end

  # -- Specific behaviour --
  def teacher_grade_levels(id)
    api_call(:get, "teachers/#{id}/grade_levels")['data'].map {|i| i.to_i}
  end

  # -- Generic behaviour for remainder of methods --

  def method_missing(sym, *args, &block)
    id, params = nil

    if args.length == 1
      params = (args[0].is_a?(Hash) ? args[0] : nil)
      id = (args[0].is_a?(Hash) ? nil : args[0])
    elsif args.length == 2
      params = (args[1].is_a?(Hash) ? args[1] : nil)
      id = args[0]
    end

    resource, action = sym.to_s.split('_')
    is_collection = resource[-1..-1] == 's' || (!action.nil? && action[-1..-1] == 's')

    if resource[-1..-1] != 's'
      resource = resource + 's'
    end

    raw_body = api_call(:get, "#{resource}/#{id}/#{action}".gsub(/\/{1,2}$/, ''), params)

    klass = eval('Clever::Models::' + (action.nil? ? resource.chomp.gsub(/s$/, '').capitalize : action.chomp.gsub(/s$/, '').capitalize))

    if is_collection
      clever_response(raw_body, raw_body['data'].map { |e| klass.new(e['data'].nil? ? e : e['data']) })
    else
      if raw_body['data'].is_a? Array
        klass.new raw_body['data'][0]['data']
      else
        if raw_body['data']['data'].nil? || !raw_body['data']['data'].is_a?(Hash)
          klass.new raw_body['data']
        else
          klass.new raw_body['data']['data']
        end
      end

    end

  #rescue
  #  super
  end

  # -- API Glue --

  # Internal: Setup the api call, format the parameters, send the request,
  # parse the response and return it.
  #
  # method   - The http verb to use, currently :get or :post
  # endpoint - The api endpoint to hit. this is the part after
  #            api.getclever.com/v1.1/.
  # params   - Parameters to send with the api call, either as a query string
  #            (get) or form params (post).
  #
  # Examples
  #
  #   api_call(:post,
  #            "districts",
  #            {})
  #   # => { "data": [{"data": "name": "Demo Distract", "id": "8e5b0721-26c4-11df-b354-002170de47d3"}]}
  #
  # Returns the json parsed response body of the call
  def api_call(method, endpoint, params={}, format=:json)

    # dispatch to the right method, with the full path (/api/v2 + endpoint)
    request = self.send("format_#{method}", "#{@url.path}/#{endpoint}", params)
    response = @http.request(request)

    # Possible Responses
    #
    # 200 - (OK) Successful request
    # 400 - (Bad Request) There was a problem with your request parameters. Check
    #       the error field of the response object for info on what was wrong.
    # 401 - (Unauthorized) Bad api_key or not authorized to access a resource.
    # 404 - (Not Found) The resource requested doesn't exist.
    # 500 - Server errors. Clever screwed up.

    unless response.code == '200'
      raise Clever::Error.new response.code, response.body
    end

    if format == :json
      return JSON.parse(response.body)
    end

    response.body
  end

  # Internal: Format and create a Net::HTTP get request, with query
  # parameters.
  #
  # path - the path to get
  # params - the params to add as query params to the path
  #
  # Examples
  #
  #   format_post("/api.getclever.com/v1.1/districts",
  #              {})
  #   # => <Net::HTTP::Get:<id>> for
  #   #    "/api.getclever.com/v1.1/districts"
  #
  # Returns a Net::HTTP::Get object for the path with query params
  def format_get(path, params)
    unless params.nil?
      query = params.map { |k, v| "#{k}=#{URI::escape(v.to_s)}" }.join("&")
    end

    request = Net::HTTP::Get.new("#{path}?#{query}")
    request.basic_auth(Clever.config.api_key, '')

    request
  end

  def clever_response raw_body, objects
    paging = if raw_body['paging'].nil?
               nil
             else
               {
                   :current => raw_body['paging']['current'],
                   :total => raw_body['paging']['total'],
                   :count => raw_body['paging']['count']
               }
             end

    {:paging => paging, :data => objects}
  end
end
