# frozen_string_literal: true

require 'json'

module Zilliqa
  module Contract
    class Contract
      include Account

      NIL_ADDRESS = '0000000000000000000000000000000000000000'

      attr_reader :factory, :provider, :signer, :code, :abi, :init, :state, :address, :status

      def initialize(factory, code, abi, address, init, state)
        @factory = factory
        @provider = factory.provider
        @signer = factory.signer

        @code = code
        @abi = abi
        @init = init
        @state = state

        if address && !address.empty?
          @address = address
          @status = ContractStatus::DEPLOYED
        else
          @status = ContractStatus::INITIALISED
        end
      end

      def initialised?
        @status == ContractStatus::INITIALISED
      end

      def deployed?
        @status == ContractStatus::DEPLOYED
      end

      def rejected?
        @status == ContractStatus::REJECTED
      end

      def deploy(deploy_params, attempts = 33, interval = 1000, _to_ds = false)
        raise 'Cannot deploy without code or initialisation parameters.' if @code.nil? || @code == ''
        raise 'Cannot deploy without code or initialisation parameters.' if @init.nil? || @init.length.zero?

        tx_params = {
          id: deploy_params.id,
          version: deploy_params.version,
          nonce: deploy_params.nonce,
          sender_pub_key: deploy_params.sender_pub_key,
          gas_price: deploy_params.gas_price,
          gas_limit: deploy_params.gas_limit,
          to_addr: NIL_ADDRESS,
          amount: '0',
          code: @code.gsub('/\\', ''),
          data: @init.to_json.gsub('\\"', '"')
        }

        tx = Transaction.new(tx_params, @provider)

        tx = prepare_tx(tx, attempts, interval)

        if tx.rejected?
          @status = ContractStatus::REJECTED

          return [tx, self]
        end

        @status = ContractStatus::DEPLOYED
        @address = ContractFactory.get_address_for_contract(tx)

        [tx, self]
      end

      def call(transition, args, params, attempts = 33, interval = 1000, to_ds = false)
        data = {
          _tag: transition,
          params: args
        }

        return 'Contract has not been deployed!' unless @address

        tx_params = {
          id: params['id'],
          version: params['version'],
          nonce: params['nonce'],
          sender_pub_key: params['sender_pub_key'],
          gas_price: params['gas_price'],
          gas_limit: params['gas_limit'],
          to_addr: @address,
          data: JSON.generate(data)
        }

        tx = Transaction.new(tx_params, @provider, Zilliqa::Account::Transaction::TX_STATUSES[:initialized], to_ds)

        prepare_tx(tx, attempts, interval)
      end

      def state
        return [] unless deployed

        response = @provider.GetSmartContractState(@address)
        response.result
      end

      def prepare_tx(tx, attempts, interval)
        tx = @signer.sign(tx)

        response = @provider.CreateTransaction(tx.to_payload)

        if response['error']
          tx.status = Zilliqa::Account::Transaction::TX_STATUSES[:rejected]
        else
          tx.confirm(response['result']['TranID'], attempts, interval)
        end

        tx
      end
    end

    class ContractStatus
      DEPLOYED = 0
      REJECTED = 1
      INITIALISED = 2
    end

    class Value
      attr_reader :vname, :type, :value
      def initialize(vname, type, value)
        @vname = vname
        @type = type
        @value = value
      end
    end

    class DeployParams
      attr_reader :id, :version, :nonce, :gas_price, :gas_limit, :sender_pub_key
      def initialize(id, version, nonce, gas_price, gas_limit, sender_pub_key)
        @id = id
        @version = version
        @nonce = nonce
        @gas_price = gas_price
        @gas_limit = gas_limit
        @sender_pub_key = sender_pub_key
      end
    end
  end
end