class Freeagent::API

def authorize

def authorize
  receiver = WEBrick::HTTPServer.new(Port: 0)
  receiver_url = "http://localhost:#{receiver.config[:Port]}/"
  url = client.auth_code.authorize_url(redirect_uri: receiver_url)
  puts "Go and authorize at #{url}, I'm waiting..."
  access_code = nil
  receiver.mount_proc '/' do |req, res|
    # TODO: parse redirect response...
    access_code = req.query['code']
    receiver.shutdown
  end
  Signal.trap('INT') do
    receiver.shutdown
  end
  receiver.start
  raise if access_code.nil?
  client.auth_code.get_token(access_code, redirect_uri: receiver_url)
end

def batch_create_timeslips timeslips

def batch_create_timeslips timeslips
  post('timeslips', {'timeslips' => timeslips})
end

def client

def client
  @client ||= OAuth2::Client.new(FREEAGENT_APP_ID, FREEAGENT_APP_SECRET,
    site: 'https://api.freeagent.com/v2/',
    authorize_url: 'approve_app',
    token_url: 'token_endpoint'
  )
end

def create_timeslip user, project, task, dated, hours

def create_timeslip user, project, task, dated, hours
  post('timeslips', {
    'task' => task.url,
    'project' => project.url,
    'user' => user.url,
    'dated_on' => dated.to_s,
    'hours' => hours,
  })
end

def delete *parts, **params

def delete *parts, **params
  relatives = parts.map(&:to_s).join('/')
  uri = URI.parse(relatives)
  puts "DELETE #{uri}"
  @token.delete(uri, params: params, headers: {'Accept': 'application/json'})
end

def delete_timeslip timeslip

def delete_timeslip timeslip
  id = timeslip.url.split('/').last
  delete('timeslips', id)
end

def first_accounting_year_end

def first_accounting_year_end
  Date.parse get('company').company.first_accounting_year_end
end

def get *parts, **params

def get *parts, **params
  relatives = parts.map(&:to_s).join('/')
  uri = URI.parse(relatives)
  puts "GET #{uri}"
  res = @token.get(uri, params: params, headers: {'Accept': 'application/json'})
  data = res.parsed
  if res.headers.include? 'Link'
    data._links = res.headers['Link'].split(", ").map do |link|
      url, rel = link.split('; rel=')
      [rel.gsub(/^'|'$/, ''), URI::parse(url.gsub(/^<|>$/, ''))]
    end.to_h
  end
  data
rescue => err
  if err.response.status == 429
    retry_after = err.response.headers['retry-after'].to_i
    STDERR.puts "Rate limited; sleeping for #{retry_after}"
    sleep retry_after
    retry
  end
end

def get_pages *path, **params

def get_pages *path, **params
  Enumerator.new do |yielder|
    loop do
      res = get(*path, **{per_page: 100}.update(params))
      yielder << res
      break if res._links.nil? || res._links.next.nil?
      params = URI::decode_www_form(res._links.next.query).to_h
    end
  end
end

def initialize

def initialize
  if FREEAGENT_APP_ID.nil? || FREEAGENT_APP_ID.empty?
    raise ArgumentError, 'FREEAGENT_APP_ID is unset'
  end
  if FREEAGENT_APP_SECRET.nil? || FREEAGENT_APP_SECRET.empty?
    raise ArgumentError, 'FREEAGENT_APP_SECRET is unset'
  end
  @token = reload || authorize
  File.write TOKEN_FILE, @token.to_hash.to_hash.to_yaml
end

def payslips year, period

def payslips year, period
  get('payroll', year, period).period.payslips
end

def periods year

def periods year
  get('payroll', year).periods
end

def post *parts, **params

def post *parts, **params
  data = parts.pop
  relatives = parts.map(&:to_s).join('/')
  uri = URI.parse(relatives)
  puts "POST #{uri}"
  body = data.to_json
  @token.post(uri, body: body, params: params, headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}).parsed
end

def profile year, user

def profile year, user
  get('payroll_profiles', year, user: user)
end

def profiles year

def profiles year
  get('payroll_profiles', year).profiles
end

def projects

def projects
  get_pages('projects').flat_map(&:projects)
end

def reload

def reload
  if File.exist? TOKEN_FILE
    OAuth2::AccessToken.from_hash(client, YAML.unsafe_load_file(TOKEN_FILE)).refresh
  end
rescue OAuth2::Error
  nil
end

def tasks project

def tasks project
  get_pages('tasks', project: project.url).flat_map(&:tasks)
end

def timeslips user: nil, project: nil, task: nil, from: nil, to: nil

def timeslips user: nil, project: nil, task: nil, from: nil, to: nil
  params = [
    [:user, user && user.url],
    [:project, project && project.url],
    [:task, task && task.url],
    [:from_date, from && from.to_s],
    [:to_date, to && to.to_s],
  ].reject {|(k, v)| v.nil?}.to_h
  get_pages('timeslips', **params).flat_map(&:timeslips)
end

def user id

def user id
  get('users', id).user
end

def users

def users
  get_pages('users').flat_map(&:users)
end