# frozen_string_literal: true

require "faraday"
require "json"

module SynorWallet
  # Synor Wallet SDK Client
  #
  # @example
  #   client = SynorWallet::Client.new(api_key: 'your-api-key')
  #
  #   # Create wallet
  #   result = client.create_wallet
  #   puts result.mnemonic
  #
  #   # Get balance
  #   balance = client.get_balance(address)
  #
  class Client
    attr_reader :config

    def initialize(api_key: nil, **options)
      @config = Config.new(api_key: api_key, **options)
      raise ArgumentError, "API key is required" unless @config.api_key

      @conn = Faraday.new(url: @config.base_url) do |f|
        f.request :json
        f.response :json
        f.options.timeout = @config.timeout
        f.headers["Authorization"] = "Bearer #{@config.api_key}"
        f.headers["X-SDK-Version"] = "ruby/#{VERSION}"
      end
      @closed = false
    end

    # ==================== Wallet Operations ====================

    # Create a new wallet
    #
    # @param type [String] Wallet type (standard, multisig, hardware)
    # @return [CreateWalletResult]
    def create_wallet(type: WalletType::STANDARD)
      check_closed!

      body = {
        type: type,
        network: @config.network
      }

      response = with_retry { @conn.post("/wallets", body) }
      CreateWalletResult.from_hash(response.body)
    end

    # Import wallet from mnemonic
    #
    # @param mnemonic [String] 12 or 24 word mnemonic phrase
    # @param passphrase [String, nil] Optional passphrase
    # @return [Wallet]
    def import_wallet(mnemonic, passphrase: nil)
      check_closed!

      body = {
        mnemonic: mnemonic,
        passphrase: passphrase,
        network: @config.network
      }

      response = with_retry { @conn.post("/wallets/import", body) }
      Wallet.from_hash(response.body)
    end

    # Get wallet by ID
    #
    # @param wallet_id [String]
    # @return [Wallet]
    def get_wallet(wallet_id)
      check_closed!

      response = with_retry { @conn.get("/wallets/#{wallet_id}") }
      Wallet.from_hash(response.body)
    end

    # Generate a new address
    #
    # @param wallet_id [String]
    # @param is_change [Boolean]
    # @return [Address]
    def generate_address(wallet_id, is_change: false)
      check_closed!

      body = { is_change: is_change }
      response = with_retry { @conn.post("/wallets/#{wallet_id}/addresses", body) }
      Address.from_hash(response.body)
    end

    # Get stealth address
    #
    # @param wallet_id [String]
    # @return [StealthAddress]
    def get_stealth_address(wallet_id)
      check_closed!

      response = with_retry { @conn.get("/wallets/#{wallet_id}/stealth-address") }
      StealthAddress.from_hash(response.body)
    end

    # ==================== Signing Operations ====================

    # Sign a transaction
    #
    # @param wallet_id [String]
    # @param transaction [Transaction]
    # @return [SignedTransaction]
    def sign_transaction(wallet_id, transaction)
      check_closed!

      body = {
        wallet_id: wallet_id,
        transaction: transaction.to_h
      }

      response = with_retry { @conn.post("/transactions/sign", body) }
      SignedTransaction.from_hash(response.body)
    end

    # Sign a message
    #
    # @param wallet_id [String]
    # @param message [String]
    # @param address_index [Integer]
    # @return [Signature]
    def sign_message(wallet_id, message, address_index: 0)
      check_closed!

      body = {
        wallet_id: wallet_id,
        message: message,
        address_index: address_index
      }

      response = with_retry { @conn.post("/messages/sign", body) }
      Signature.from_hash(response.body)
    end

    # Verify a message signature
    #
    # @param message [String]
    # @param signature [String]
    # @param address [String]
    # @return [Boolean]
    def verify_message(message, signature, address)
      check_closed!

      body = {
        message: message,
        signature: signature,
        address: address
      }

      response = with_retry { @conn.post("/messages/verify", body) }
      response.body["valid"]
    end

    # ==================== Balance & UTXOs ====================

    # Get balance for an address
    #
    # @param address [String]
    # @return [Balance]
    def get_balance(address)
      check_closed!

      response = with_retry { @conn.get("/addresses/#{address}/balance") }
      Balance.from_hash(response.body)
    end

    # Get UTXOs for an address
    #
    # @param address [String]
    # @param min_confirmations [Integer]
    # @return [Array<UTXO>]
    def get_utxos(address, min_confirmations: 1)
      check_closed!

      response = with_retry do
        @conn.get("/addresses/#{address}/utxos", min_confirmations: min_confirmations)
      end
      response.body.map { |u| UTXO.from_hash(u) }
    end

    # ==================== Fee Estimation ====================

    # Estimate fee
    #
    # @param priority [String]
    # @return [Integer] Fee per byte in satoshis
    def estimate_fee(priority: Priority::MEDIUM)
      check_closed!

      response = with_retry { @conn.get("/fees/estimate", priority: priority) }
      response.body["fee_per_byte"]
    end

    # ==================== Lifecycle ====================

    def close
      @closed = true
      @conn.close if @conn.respond_to?(:close)
    end

    def closed?
      @closed
    end

    private

    def check_closed!
      raise ClientClosedError, "Client has been closed" if @closed
    end

    def with_retry
      attempts = 0
      begin
        attempts += 1
        response = yield
        handle_response(response)
        response
      rescue Faraday::Error, ApiError => e
        if attempts < @config.retries
          sleep(attempts)
          retry
        end
        raise
      end
    end

    def handle_response(response)
      return if response.success?

      error_message = response.body["error"] || response.body["message"] || "Unknown error"
      raise ApiError.new(error_message, status_code: response.status)
    end
  end
end
