# frozen_string_literal: true

require "addressable"
require "http"
require "json"

require "qiscus/omnichannel/error"
require "qiscus/version"
require "qiscus/omnichannel/api/rooms"
require "qiscus/omnichannel/api/agents"
require "qiscus/omnichannel/api/divisions"
require "qiscus/omnichannel/api/bots"
require "qiscus/omnichannel/api/webhooks"
require "qiscus/omnichannel/api/settings"
require "qiscus/omnichannel/api/channels"

module Qiscus
  module Omnichannel
    #
    # Qiscus Omnichannel API client
    #
    class Client
      attr_reader :app_id, :secret_key, :url

      DEFAULT_URL = "https://multichannel.qiscus.com"
      DEFAULT_TIMEOUT = 30

      include Qiscus::Omnichannel::API::Rooms
      include Qiscus::Omnichannel::API::Agents
      include Qiscus::Omnichannel::API::Divisions
      include Qiscus::Omnichannel::API::Bots
      include Qiscus::Omnichannel::API::Webhooks
      include Qiscus::Omnichannel::API::Settings
      include Qiscus::Omnichannel::API::Channels

      #
      # Initialize new Qiscus Omnichannel client
      #
      # @param app_id [String]
      # @param secret_key [String]
      # @param opt [Hash]
      #
      def initialize(app_id = nil, secret_key = nil, opt = {})
        @app_id = app_id
        @secret_key = secret_key
        @url = Addressable::URI.parse(opt[:url] || DEFAULT_URL)
      end

      #
      # HTTP post request
      #
      # @param path [String]
      # @param bind [Array]
      # @param body [Hash]
      # @return [Hash]
      #
      def post(path, bind = [], body = {})
        opt = {
          json: body,
          headers: auth_headers,
        }

        request("post", path, bind, opt)
      end

      #
      # HTTP post request without adding auth headers
      #
      # @param path [String]
      # @param bind [Array]
      # @param body [Hash]
      # @return [Hash]
      #
      def public_post(path, bind = [], body = {})
        request("post", path, bind, json: body)
      end

      #
      # HTTP delete request
      #
      # @param path [String]
      # @param bind [Array]
      # @param params [Hash]
      # @return [Hash]
      #
      def delete(path, bind = [], params = {})
        opt = {
          params: params,
          headers: auth_headers,
        }

        request("delete", path, bind, opt)
      end

      #
      # HTTP get request
      #
      # @param path [String]
      # @param bind [Array]
      # @param params [Hash]
      # @return [Hash]
      #
      def get(path, bind = [], params = {})
        opt = {
          params: params,
          headers: auth_headers,
        }

        request("get", path, bind, opt)
      end

      #
      # HTTP request
      #
      # @param method [String,Symbol]
      # @param path [String]
      # @param bind [Array]
      # @param opt [Hash]
      # @return [Hash]
      #
      def request(method, path, bind = [], opt = {})
        res = connection.request(method, url.join(path), opt)
        puts("BODY. Status: #{res.code}")

        if res.status.success?
          body_str = res.to_s
          body = JSON.parse(body_str) || {}

          unless bind.nil? || bind.empty?
            raise ArgumentError, ":bind argument must be Array" unless bind.is_a?(Array)

            bind = bind.map(&:to_s)

            body = body.dig(*bind)
          end

          body
        else
          handle_error(res)
        end
      end

      private

      # @return [HTTP::Client]
      def connection
        @connection ||= HTTP.timeout(DEFAULT_TIMEOUT).headers({ "User-Agent": "Qiscus Ruby v#{Qiscus::VERSION}" })
      end

      # @return [Hash]
      def auth_headers
        { "Qiscus-App-Id": app_id, "Qiscus-Secret-Key": secret_key }
      end

      #
      # @param res [HTTP::Response]
      # @return [Hash]
      #
      def parse_response_body(res)
        JSON.parse(res.to_s)
      end

      #
      # @param body [Hash]
      # @return [String]
      #
      def parse_error_message(body, default = nil)
        if body.dig("errors").is_a?(Hash)
          body.dig("errors", "message")
        elsif body.dig("errors", "messages").is_a?(String)
          body.dig("errors", "messages")
        else
          message = default
          message = body.to_json if body.nil? || body.empty?
          message
        end
      end

      #
      # @param res [HTTP::Response]
      # @return [NilClass]
      #
      def handle_error(res)
        puts res.to_s

        case res.code
        when 401
          raise UnauthorizedError, "Invalid credentials"
        when 402
          raise PaymentRequiredError, "Your app has been suspended/deactivated due to payment. Please contact our team"
        when 403
          raise ForbiddenError, "You are not authorized to do this request"
        when 404
          raise NotFoundError, parse_error_message(parse_response_body(res), "The resource you need is not found")
        when 422
          raise UnprocessableEntityError, parse_error_message(parse_response_body(res), "Invalid params")
        when 429
          raise TooManyRequestsError, "Too many request in one window. Please try again later"
        when 400
          raise BadRequestError, parse_error_message(parse_response_body(res))
        when 500..600
          raise ServerError, "Something wrong on Qiscus Omnichannel's side. Please contact. our team"
        else
          raise Error, "Got #{res.code} while requesting to Qiscus Omnichannel"
        end
      end
    end
  end
end
