# frozen_string_literal: true
require 'excon'

class CustomWizard::Api::Authorization
  include ActiveModel::SerializerSupport

  attr_accessor :api_name,
                :authorized,
                :auth_type,
                :auth_url,
                :token_url,
                :client_id,
                :client_secret,
                :auth_params,
                :access_token,
                :refresh_token,
                :token_expires_at,
                :token_refresh_at,
                :code,
                :username,
                :password

  def initialize(api_name, data = {})
    @api_name = api_name

    data.each do |k, v|
      self.send "#{k}=", v if self.respond_to?(k)
    end
  end

  def authorized
    @authorized ||= @access_token && @token_expires_at.to_datetime > Time.now
  end

  def self.set(api_name, new_data = {})

    api_name = api_name.underscore

    data = self.get(api_name, data_only: true) || {}

    new_data.each do |k, v|
      data[k.to_sym] = v
    end

    PluginStore.set("custom_wizard_api_#{api_name}", 'authorization', data)

    self.get(api_name)
  end

  def self.get(api_name, opts = {})
    api_name = api_name.underscore

    if data = PluginStore.get("custom_wizard_api_#{api_name}", 'authorization')
      if opts[:data_only]
        data
      else
        self.new(api_name, data)
      end
    else
      nil
    end
  end

  def self.remove(api_name)
    PluginStore.remove("custom_wizard_api_#{api_name}", "authorization")
  end

  def self.authorization_string(name)
    auth = CustomWizard::Api::Authorization.get(name)
    raise Discourse::InvalidParameters.new(:name) unless auth.present?

    if auth.auth_type === "basic"
      raise Discourse::InvalidParameters.new(:username) unless auth.username.present?
      raise Discourse::InvalidParameters.new(:password) unless auth.password.present?
      "Basic #{Base64.strict_encode64((auth.username + ":" + auth.password).chomp)}"
    elsif ['oauth_3', 'oauth_2'].include?(auth.auth_type)
      raise Discourse::InvalidParameters.new(auth.access_token) unless auth.access_token.present?
      "Bearer #{auth.access_token}"
    else
      nil
    end
  end

  def self.get_token(name, opts = {})
    authorization = CustomWizard::Api::Authorization.get(name)
    type = authorization.auth_type

    body = {}

    if opts[:refresh] && type === 'oauth_3'
      body['grant_type'] = 'refresh_token'
    elsif type === 'oauth_2'
      body['grant_type'] = 'client_credentials'
    elsif type === 'oauth_3'
      body['grant_type'] = 'authorization_code'
    end

    unless opts[:refresh]
      body['client_id'] = authorization.client_id
      body['client_secret'] = authorization.client_secret
    end

    if type === 'oauth_3'
      body['code'] = authorization.code
      body['redirect_uri'] = Discourse.base_url + "/admin/wizards/apis/#{name}/redirect"
    end

    connection = Excon.new(
      authorization.token_url,
      headers: {
        "Content-Type" => "application/x-www-form-urlencoded"
      },
      method: 'GET',
      query: URI.encode_www_form(body)
    )
    begin
      result = connection.request()
      log_params = { time: Time.now, user_id: 0, status: 'SUCCESS', url: authorization.token_url, error: "" }
      CustomWizard::Api::LogEntry.set(name, log_params)
    rescue SystemCallError => e
      log_params = { time: Time.now, user_id: 0, status: 'FAILURE', url: authorization.token_url, error: "Token refresh request failed: #{e.inspect}" }
      CustomWizard::Api::LogEntry.set(name, log_params)
    end

    self.handle_token_result(name, result)
  end

  def self.handle_token_result(name, result)
    result_data = JSON.parse(result.body)

    if result_data['error']
      return result_data
    end

    data = {}

    data['access_token'] = result_data['access_token']
    data['refresh_token'] = result_data['refresh_token'] if result_data['refresh_token']
    data['token_type'] = result_data['token_type'] if result_data['token_type']

    if result_data['expires_in']
      data['token_expires_at'] = Time.now + result_data['expires_in'].seconds
      data['token_refresh_at'] = data['token_expires_at'].to_time - 10.minutes

      opts = {
        name: name
      }

      Jobs.enqueue_at(data['token_refresh_at'], :refresh_api_access_token, opts)
    end

    CustomWizard::Api::Authorization.set(name, data)
  end
end
