# frozen_string_literal: true

module SynorRpc
  # Network environment
  module Network
    MAINNET = "mainnet"
    TESTNET = "testnet"
    DEVNET = "devnet"
  end

  # Transaction priority
  module Priority
    LOW = "low"
    MEDIUM = "medium"
    HIGH = "high"
  end

  # Transaction status
  module TransactionStatus
    PENDING = "pending"
    CONFIRMED = "confirmed"
    FAILED = "failed"
  end

  # Configuration
  class Config
    attr_reader :api_key, :base_url, :ws_url, :network, :timeout, :retries, :debug

    def initialize(
      api_key:,
      base_url: "https://rpc.synor.io/v1",
      ws_url: "wss://rpc.synor.io/v1/ws",
      network: Network::MAINNET,
      timeout: 30,
      retries: 3,
      debug: false
    )
      @api_key = api_key
      @base_url = base_url
      @ws_url = ws_url
      @network = network
      @timeout = timeout
      @retries = retries
      @debug = debug
    end
  end

  # Block header
  class BlockHeader
    attr_reader :hash, :height, :previous_hash, :timestamp, :version,
                :merkle_root, :nonce, :difficulty

    def initialize(hash:, height:, previous_hash:, timestamp:, version:,
                   merkle_root:, nonce:, difficulty:)
      @hash = hash
      @height = height
      @previous_hash = previous_hash
      @timestamp = timestamp
      @version = version
      @merkle_root = merkle_root
      @nonce = nonce
      @difficulty = difficulty
    end

    def self.from_hash(h)
      new(
        hash: h["hash"],
        height: h["height"],
        previous_hash: h["previous_hash"],
        timestamp: h["timestamp"],
        version: h["version"],
        merkle_root: h["merkle_root"],
        nonce: h["nonce"],
        difficulty: h["difficulty"]
      )
    end
  end

  # Script public key
  class ScriptPubKey
    attr_reader :asm, :hex, :type, :address

    def initialize(asm:, hex:, type:, address: nil)
      @asm = asm
      @hex = hex
      @type = type
      @address = address
    end

    def self.from_hash(h)
      new(
        asm: h["asm"],
        hex: h["hex"],
        type: h["type"],
        address: h["address"]
      )
    end
  end

  # Transaction input
  class TransactionInput
    attr_reader :txid, :vout, :script_sig, :sequence, :witness

    def initialize(txid:, vout:, script_sig:, sequence:, witness: nil)
      @txid = txid
      @vout = vout
      @script_sig = script_sig
      @sequence = sequence
      @witness = witness
    end

    def self.from_hash(h)
      new(
        txid: h["txid"],
        vout: h["vout"],
        script_sig: h["script_sig"],
        sequence: h["sequence"],
        witness: h["witness"]
      )
    end
  end

  # Transaction output
  class TransactionOutput
    attr_reader :value, :n, :script_pubkey

    def initialize(value:, n:, script_pubkey:)
      @value = value
      @n = n
      @script_pubkey = script_pubkey
    end

    def self.from_hash(h)
      new(
        value: h["value"],
        n: h["n"],
        script_pubkey: ScriptPubKey.from_hash(h["script_pubkey"])
      )
    end
  end

  # Transaction
  class Transaction
    attr_reader :txid, :hash, :version, :size, :weight, :lock_time,
                :inputs, :outputs, :fee, :confirmations,
                :block_hash, :block_height, :timestamp, :status

    def initialize(txid:, hash:, version:, size:, weight:, lock_time:,
                   inputs:, outputs:, fee:, confirmations:,
                   block_hash: nil, block_height: nil, timestamp: nil,
                   status: TransactionStatus::PENDING)
      @txid = txid
      @hash = hash
      @version = version
      @size = size
      @weight = weight
      @lock_time = lock_time
      @inputs = inputs
      @outputs = outputs
      @fee = fee
      @confirmations = confirmations
      @block_hash = block_hash
      @block_height = block_height
      @timestamp = timestamp
      @status = status
    end

    def self.from_hash(h)
      new(
        txid: h["txid"],
        hash: h["hash"],
        version: h["version"],
        size: h["size"],
        weight: h["weight"],
        lock_time: h["lock_time"],
        inputs: (h["inputs"] || []).map { |i| TransactionInput.from_hash(i) },
        outputs: (h["outputs"] || []).map { |o| TransactionOutput.from_hash(o) },
        fee: h["fee"],
        confirmations: h["confirmations"],
        block_hash: h["block_hash"],
        block_height: h["block_height"],
        timestamp: h["timestamp"],
        status: h["status"] || TransactionStatus::PENDING
      )
    end
  end

  # Block
  class Block
    attr_reader :hash, :height, :previous_hash, :timestamp, :version,
                :merkle_root, :nonce, :difficulty, :transactions, :size, :weight

    def initialize(hash:, height:, previous_hash:, timestamp:, version:,
                   merkle_root:, nonce:, difficulty:, transactions:, size:, weight:)
      @hash = hash
      @height = height
      @previous_hash = previous_hash
      @timestamp = timestamp
      @version = version
      @merkle_root = merkle_root
      @nonce = nonce
      @difficulty = difficulty
      @transactions = transactions
      @size = size
      @weight = weight
    end

    def self.from_hash(h)
      new(
        hash: h["hash"],
        height: h["height"],
        previous_hash: h["previous_hash"],
        timestamp: h["timestamp"],
        version: h["version"],
        merkle_root: h["merkle_root"],
        nonce: h["nonce"],
        difficulty: h["difficulty"],
        transactions: (h["transactions"] || []).map { |t| Transaction.from_hash(t) },
        size: h["size"],
        weight: h["weight"]
      )
    end
  end

  # Chain info
  class ChainInfo
    attr_reader :chain, :blocks, :headers, :best_block_hash, :difficulty,
                :median_time, :verification_progress, :chain_work, :pruned

    def initialize(chain:, blocks:, headers:, best_block_hash:, difficulty:,
                   median_time:, verification_progress:, chain_work:, pruned:)
      @chain = chain
      @blocks = blocks
      @headers = headers
      @best_block_hash = best_block_hash
      @difficulty = difficulty
      @median_time = median_time
      @verification_progress = verification_progress
      @chain_work = chain_work
      @pruned = pruned
    end

    def self.from_hash(h)
      new(
        chain: h["chain"],
        blocks: h["blocks"],
        headers: h["headers"],
        best_block_hash: h["best_block_hash"],
        difficulty: h["difficulty"],
        median_time: h["median_time"],
        verification_progress: h["verification_progress"],
        chain_work: h["chain_work"],
        pruned: h["pruned"]
      )
    end
  end

  # Mempool info
  class MempoolInfo
    attr_reader :size, :bytes, :usage, :max_mempool, :mempool_min_fee, :min_relay_tx_fee

    def initialize(size:, bytes:, usage:, max_mempool:, mempool_min_fee:, min_relay_tx_fee:)
      @size = size
      @bytes = bytes
      @usage = usage
      @max_mempool = max_mempool
      @mempool_min_fee = mempool_min_fee
      @min_relay_tx_fee = min_relay_tx_fee
    end

    def self.from_hash(h)
      new(
        size: h["size"],
        bytes: h["bytes"],
        usage: h["usage"],
        max_mempool: h["max_mempool"],
        mempool_min_fee: h["mempool_min_fee"],
        min_relay_tx_fee: h["min_relay_tx_fee"]
      )
    end
  end

  # Fee estimate
  class FeeEstimate
    attr_reader :priority, :fee_rate, :estimated_blocks

    def initialize(priority:, fee_rate:, estimated_blocks:)
      @priority = priority
      @fee_rate = fee_rate
      @estimated_blocks = estimated_blocks
    end

    def self.from_hash(h)
      new(
        priority: h["priority"],
        fee_rate: h["fee_rate"],
        estimated_blocks: h["estimated_blocks"]
      )
    end
  end

  # Submit result
  class SubmitResult
    attr_reader :txid, :accepted, :reason

    def initialize(txid:, accepted:, reason: nil)
      @txid = txid
      @accepted = accepted
      @reason = reason
    end

    def self.from_hash(h)
      new(
        txid: h["txid"],
        accepted: h["accepted"],
        reason: h["reason"]
      )
    end
  end

  # Subscription handle
  class Subscription
    attr_reader :id, :channel

    def initialize(id:, channel:, &on_cancel)
      @id = id
      @channel = channel
      @on_cancel = on_cancel
      @cancelled = false
    end

    def cancel
      return if @cancelled

      @on_cancel&.call
      @cancelled = true
    end

    def cancelled?
      @cancelled
    end
  end
end
