# frozen_string_literal: true

require_relative "pesepay/version"
require "cgi"
require "digest"
require "uri"
require "net/http"
require "openssl"
require "base64"
require "json"

module Pesepay
  class Pesepay
    attr_accessor :result_url, :return_url

    def initialize(integration_key, encryption_key)
      @integration_key = integration_key
      @encryption_key = encryption_key
    end

    def create_transaction(amount, currency_code, reason_for_payment, reference)
      Transaction.new(amount, currency_code, reason_for_payment, reference)
    end

    def create_seamless_transaction(currency_code, payment_method_code, customer_email = nil, customer_phone = nil, customer_name = nil)
      Payment.new(currency_code, payment_method_code, customer_email, customer_phone, customer_name)
    end

    def initiate_transaction(transaction)
      response = create_transaction_request(transaction)
      process_response(response)
    end

    def make_seamless_payment(payment, reason_for_payment, amount, payment_method_required_fields, reference = nil)
      response = create_seamless_payment_request(payment, reason_for_payment, amount, payment_method_required_fields, reference)
      process_response(response)
    end

    def get_payment_method_code(currency_code)
      response = get_payment_method_code_request(currency_code)
      process_get_code_method(response)
    end

    def poll_transaction(poll_url)
      url = URI(poll_url)

      http = Net::HTTP.new(url.host, url.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE

      request = Net::HTTP::Get.new(url)
      request["Authorization"] = @integration_key
      request["Content-Type"] = "application/json"
      response = http.request(request)

      process_poll_response(response)
    end

    def check_payment(reference_number)
      url = "https://api.pesepay.com/api/payments-engine/v1/payments/check-payment?referenceNumber=#{reference_number}"
      poll_transaction(url)
    end

    private

    def create_transaction_request(transaction)
      data = build_transaction_data(transaction)

      url = URI("https://api.pesepay.com/api/payments-engine/v1/payments/initiate")

      http = Net::HTTP.new(url.host, url.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE

      request = Net::HTTP::Post.new(url)
      request["Authorization"] = @integration_key
      request["Content-Type"] = "application/json"
      request.body = { payload: data }.to_json
      http.request(request)
    end

    def create_seamless_payment_request(payment, reason_for_payment, amount, payment_method_required_fields, reference)
      data = build_seamless_payment_data(payment, reason_for_payment, amount, payment_method_required_fields, reference)

      url = URI("https://api.pesepay.com/api/payments-engine/v2/payments/make-payment")

      http = Net::HTTP.new(url.host, url.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE

      request = Net::HTTP::Post.new(url)
      request["Authorization"] = @integration_key
      request["Content-Type"] = "application/json"
      request.body = { payload: data }.to_json
      http.request(request)
    end

    def get_payment_method_code_request(currency_code)
      url = URI("https://api.pesepay.com/api/payments-engine/v1/payment-methods/for-currency?currencyCode=#{currency_code}")

      http = Net::HTTP.new(url.host, url.port)
      http.use_ssl = true
      http.verify_mode = OpenSSL::SSL::VERIFY_NONE

      request = Net::HTTP::Get.new(url)
      http.request(request)
    end

    def process_get_code_method(response)
      if response.code == "200"
        response_body = JSON.parse(response.body)
        payment_method_code = response_body[0]["code"]
        return payment_method_code
      else
        Response.new(false, message: "Unable to get payment method code")
      end
    end

    def process_response(response)
      if response.code == "200"
        response_body = JSON.parse(response.body.gsub("'", '"'))
        inner_payload = response_body["payload"]
        raw_response = decrypt(inner_payload, @encryption_key)
        json_string = JSON.parse(raw_response)
        ref_no = json_string["referenceNumber"]
        poll_url = json_string["pollUrl"]
        redirect_url = json_string["redirectUrl"]
        Response.new(true, referenceNumber: ref_no, pollUrl: poll_url, redirectUrl: redirect_url)
      else
        message = JSON.parse(response.body)["message"]
        Response.new(false, message: message)
      end
    end

    def process_poll_response(response)
      if response.code == "200"
        response_body = JSON.parse(response.body)
        inner_payload = response_body["payload"]
        raw_response = decrypt(inner_payload)
        json_string = JSON.parse(raw_response)
        reference_number = json_string["referenceNumber"]
        poll_url = json_string["pollUrl"]
        paid = json_string["transactionStatus"] == "SUCCESS"

        StatusResponse.new(reference_number, poll_url, paid)
      else
        message = JSON.parse(response.body)["message"]
        StatusResponse.new(nil, nil, false, message)
      end
    end

    def build_transaction_data(transaction)
      payload = {
        "amountDetails": {
          "amount": transaction.amount,
          "currencyCode": transaction.currency_code,
        },
        "reasonForPayment": transaction.reason_for_payment,
        "resultUrl": @result_url,
        "returnUrl": @return_url,
        "merchantReference": transaction.reference,
      }

      encrypted = encrypt(payload.to_json, @encryption_key)
      encrypted
    end

    def build_seamless_payment_data(payment, reason_for_payment, amount, payment_method_required_fields, reference)
      payload = {
        "amountDetails": {
          "amount": amount,
          "currencyCode": payment.currency_code,
        },
        "merchantReference": reference,
        "reasonForPayment": reason_for_payment,
        "resultUrl": @result_url,
        "returnUrl": @return_url,
        "paymentMethodCode": payment.payment_method_code,
        "customer": {
          "email": payment.customer_email,
          "phoneNumber": payment.customer_phone,
          "name": payment.customer_name,
        },
        "paymentMethodRequiredFields": payment_method_required_fields,
      }

      encrypted = encrypt(payload.to_json, @encryption_key)
      encrypted
    end

    def encrypt(payload, key)
      init_vector = key[0, 16]
      cryptor = OpenSSL::Cipher::AES.new(256, :CBC)
      cryptor.encrypt
      cryptor.key = key
      cryptor.iv = init_vector
      ciphertext = cryptor.update(pad(payload)) + cryptor.final
      Base64.strict_encode64(ciphertext)
    end

    def decrypt(payload, key)
      ciphertext = Base64.strict_decode64(payload)
      init_vector = key[0, 16]
      cryptor = OpenSSL::Cipher::AES.new(256, :CBC)
      cryptor.decrypt
      cryptor.key = key
      cryptor.iv = init_vector
      plaintext = cryptor.update(ciphertext) + cryptor.final
      plaintext
    end

    def pad(input)
      padding_length = 16 - (input.length % 16)
      input + (padding_length.chr * padding_length)
    end
  end

  class Response
    attr_reader :success, :referenceNumber, :pollUrl, :redirectUrl, :message

    def initialize(success, referenceNumber: nil, pollUrl: nil, redirectUrl: nil, message: nil)
      @success = success
      @referenceNumber = referenceNumber
      @pollUrl = pollUrl
      @redirectUrl = redirectUrl
      @message = message
    end
  end

  class StatusResponse
    attr_reader :referenceNumber, :pollUrl, :paid

    def initialize(referenceNumber: nil, pollUrl: nil, paid: false)
      @paid = paid
      @referenceNumber = referenceNumber
      @pollUrl = pollUrl
    end
  end

  class Transaction
    attr_accessor :amount, :currency_code, :reason_for_payment, :reference

    def initialize(amount, currency_code, reason_for_payment, reference)
      @amount = amount
      @currency_code = currency_code
      @reason_for_payment = reason_for_payment
      @reference = reference
    end
  end

  class Payment
    attr_accessor :currency_code, :payment_method_code, :customer_email, :customer_phone, :customer_name

    def initialize(currency_code, payment_method_code, customer_email = nil, customer_phone = nil, customer_name = nil)
      @currency_code = currency_code
      @payment_method_code = payment_method_code
      @customer_email = customer_email
      @customer_phone = customer_phone
      @customer_name = customer_name
    end
  end
end