# frozen_string_literal: true

require 'net/http'
require 'uri'
require 'json'
require 'base64'

module Synor
  # ZK SDK for Ruby
  #
  # Zero-Knowledge proof systems for ZK-Rollups and privacy.
  module Zk
    VERSION = '0.1.0'

    # Proof system backends
    module ProofSystem
      GROTH16 = 'groth16' # Smallest proofs (~192 bytes), fastest verification
      PLONK = 'plonk'     # Universal trusted setup, medium proofs (~512 bytes)
      STARK = 'stark'     # No trusted setup, largest proofs (~50KB)
    end

    # Circuit types for different operations
    module CircuitType
      TRANSFER = 'transfer' # Single transfer between accounts
      BATCH = 'batch'       # Batch of multiple transfers
      DEPOSIT = 'deposit'   # Deposit from L1 to L2
      WITHDRAW = 'withdraw' # Withdrawal from L2 to L1
    end

    # Rollup state
    module RollupState
      ACTIVE = 'active'
      PAUSED = 'paused'
      FINALIZING = 'finalizing'
      FINALIZED = 'finalized'
    end

    # Proof status
    module ProofStatus
      GENERATING = 'generating'
      COMPLETED = 'completed'
      FAILED = 'failed'
      VERIFIED = 'verified'
    end

    # Zero-knowledge proof
    class Proof
      attr_reader :id, :system, :circuit_type, :data, :public_inputs,
                  :size, :generation_time_ms, :created_at

      def initialize(id:, system:, circuit_type:, data:, public_inputs:,
                     size:, generation_time_ms:, created_at:)
        @id = id
        @system = system
        @circuit_type = circuit_type
        @data = data
        @public_inputs = public_inputs
        @size = size
        @generation_time_ms = generation_time_ms
        @created_at = created_at
      end

      def self.from_json(json)
        Proof.new(
          id: json['id'],
          system: json['system'],
          circuit_type: json['circuit_type'],
          data: json['data'],
          public_inputs: json['public_inputs'] || [],
          size: json['size'] || 0,
          generation_time_ms: json['generation_time_ms'] || 0,
          created_at: json['created_at'] || 0
        )
      end

      def to_json(*_args)
        {
          'id' => @id,
          'system' => @system,
          'circuit_type' => @circuit_type,
          'data' => @data,
          'public_inputs' => @public_inputs,
          'size' => @size,
          'generation_time_ms' => @generation_time_ms,
          'created_at' => @created_at
        }
      end
    end

    # Verification key
    class VerificationKey
      attr_reader :circuit_id, :circuit_type, :system, :data, :size

      def initialize(circuit_id:, circuit_type:, system:, data:, size:)
        @circuit_id = circuit_id
        @circuit_type = circuit_type
        @system = system
        @data = data
        @size = size
      end

      def self.from_json(json)
        VerificationKey.new(
          circuit_id: json['circuit_id'],
          circuit_type: json['circuit_type'],
          system: json['system'],
          data: json['data'],
          size: json['size'] || 0
        )
      end
    end

    # Proving key
    class ProvingKey
      attr_reader :circuit_id, :circuit_type, :system, :data, :size

      def initialize(circuit_id:, circuit_type:, system:, data:, size:)
        @circuit_id = circuit_id
        @circuit_type = circuit_type
        @system = system
        @data = data
        @size = size
      end

      def self.from_json(json)
        ProvingKey.new(
          circuit_id: json['circuit_id'],
          circuit_type: json['circuit_type'],
          system: json['system'],
          data: json['data'],
          size: json['size'] || 0
        )
      end
    end

    # Circuit configuration
    class CircuitConfig
      attr_reader :type, :max_batch_size, :tree_depth, :verify_signatures, :custom_constraints

      def initialize(type:, max_batch_size: nil, tree_depth: nil,
                     verify_signatures: nil, custom_constraints: nil)
        @type = type
        @max_batch_size = max_batch_size
        @tree_depth = tree_depth
        @verify_signatures = verify_signatures
        @custom_constraints = custom_constraints
      end

      def self.from_json(json)
        CircuitConfig.new(
          type: json['type'],
          max_batch_size: json['max_batch_size'],
          tree_depth: json['tree_depth'],
          verify_signatures: json['verify_signatures'],
          custom_constraints: json['custom_constraints']
        )
      end

      def to_json(*_args)
        result = { 'type' => @type }
        result['max_batch_size'] = @max_batch_size if @max_batch_size
        result['tree_depth'] = @tree_depth if @tree_depth
        result['verify_signatures'] = @verify_signatures unless @verify_signatures.nil?
        result['custom_constraints'] = @custom_constraints if @custom_constraints
        result
      end
    end

    # Account state
    class AccountState
      attr_reader :address, :balance, :nonce, :pubkey_hash

      def initialize(address:, balance:, nonce:, pubkey_hash:)
        @address = address
        @balance = balance
        @nonce = nonce
        @pubkey_hash = pubkey_hash
      end

      def self.from_json(json)
        AccountState.new(
          address: json['address'],
          balance: json['balance'],
          nonce: json['nonce'] || 0,
          pubkey_hash: json['pubkey_hash'] || ''
        )
      end
    end

    # Transfer operation
    class Transfer
      attr_reader :from, :to, :amount, :fee, :nonce, :signature

      def initialize(from:, to:, amount:, fee:, nonce:, signature: nil)
        @from = from
        @to = to
        @amount = amount
        @fee = fee
        @nonce = nonce
        @signature = signature
      end

      def self.from_json(json)
        Transfer.new(
          from: json['from'],
          to: json['to'],
          amount: json['amount'],
          fee: json['fee'],
          nonce: json['nonce'] || 0,
          signature: json['signature']
        )
      end

      def to_json(*_args)
        result = { 'from' => @from, 'to' => @to, 'amount' => @amount, 'fee' => @fee, 'nonce' => @nonce }
        result['signature'] = @signature if @signature
        result
      end
    end

    # Deposit operation
    class Deposit
      attr_reader :l1_tx_hash, :recipient, :amount, :token

      def initialize(l1_tx_hash:, recipient:, amount:, token: nil)
        @l1_tx_hash = l1_tx_hash
        @recipient = recipient
        @amount = amount
        @token = token
      end

      def to_json(*_args)
        result = { 'l1_tx_hash' => @l1_tx_hash, 'recipient' => @recipient, 'amount' => @amount }
        result['token'] = @token if @token
        result
      end
    end

    # Withdrawal operation
    class Withdrawal
      attr_reader :sender, :recipient, :amount, :token, :nonce, :signature

      def initialize(sender:, recipient:, amount:, nonce:, token: nil, signature: nil)
        @sender = sender
        @recipient = recipient
        @amount = amount
        @token = token
        @nonce = nonce
        @signature = signature
      end

      def to_json(*_args)
        result = { 'sender' => @sender, 'recipient' => @recipient, 'amount' => @amount, 'nonce' => @nonce }
        result['token'] = @token if @token
        result['signature'] = @signature if @signature
        result
      end
    end

    # Rollup batch
    class Batch
      attr_reader :batch_number, :prev_state_root, :new_state_root,
                  :transfers, :deposits, :withdrawals, :proof, :timestamp

      def initialize(batch_number:, prev_state_root:, new_state_root:, transfers:,
                     deposits:, withdrawals:, proof: nil, timestamp:)
        @batch_number = batch_number
        @prev_state_root = prev_state_root
        @new_state_root = new_state_root
        @transfers = transfers
        @deposits = deposits
        @withdrawals = withdrawals
        @proof = proof
        @timestamp = timestamp
      end

      def self.from_json(json)
        Batch.new(
          batch_number: json['batch_number'],
          prev_state_root: json['prev_state_root'],
          new_state_root: json['new_state_root'],
          transfers: (json['transfers'] || []).map { |t| Transfer.from_json(t) },
          deposits: json['deposits'] || [],
          withdrawals: json['withdrawals'] || [],
          proof: json['proof'] ? Proof.from_json(json['proof']) : nil,
          timestamp: json['timestamp'] || 0
        )
      end
    end

    # Rollup statistics
    class RollupStats
      attr_reader :current_batch, :total_transactions, :total_proofs,
                  :avg_proof_time_ms, :state_root, :account_count, :tvl

      def initialize(current_batch:, total_transactions:, total_proofs:,
                     avg_proof_time_ms:, state_root:, account_count:, tvl:)
        @current_batch = current_batch
        @total_transactions = total_transactions
        @total_proofs = total_proofs
        @avg_proof_time_ms = avg_proof_time_ms
        @state_root = state_root
        @account_count = account_count
        @tvl = tvl
      end

      def self.from_json(json)
        RollupStats.new(
          current_batch: json['current_batch'],
          total_transactions: json['total_transactions'] || 0,
          total_proofs: json['total_proofs'] || 0,
          avg_proof_time_ms: json['avg_proof_time_ms'] || 0,
          state_root: json['state_root'],
          account_count: json['account_count'] || 0,
          tvl: json['tvl'] || '0'
        )
      end
    end

    # Proof request
    class ProofRequest
      attr_reader :circuit_type, :public_inputs, :private_inputs, :system

      def initialize(circuit_type:, public_inputs:, private_inputs:, system: nil)
        @circuit_type = circuit_type
        @public_inputs = public_inputs
        @private_inputs = private_inputs
        @system = system
      end

      def to_json(*_args)
        result = {
          'circuit_type' => @circuit_type,
          'public_inputs' => @public_inputs,
          'private_inputs' => @private_inputs
        }
        result['system'] = @system if @system
        result
      end
    end

    # Verification result
    class VerificationResult
      attr_reader :valid, :verification_time_ms, :error

      def initialize(valid:, verification_time_ms:, error: nil)
        @valid = valid
        @verification_time_ms = verification_time_ms
        @error = error
      end

      def self.from_json(json)
        VerificationResult.new(
          valid: json['valid'],
          verification_time_ms: json['verification_time_ms'] || 0,
          error: json['error']
        )
      end
    end

    # Merkle proof
    class MerkleProof
      attr_reader :leaf, :path, :indices, :root

      def initialize(leaf:, path:, indices:, root:)
        @leaf = leaf
        @path = path
        @indices = indices
        @root = root
      end

      def self.from_json(json)
        MerkleProof.new(
          leaf: json['leaf'],
          path: json['path'],
          indices: json['indices'],
          root: json['root']
        )
      end

      def to_json(*_args)
        { 'leaf' => @leaf, 'path' => @path, 'indices' => @indices, 'root' => @root }
      end
    end

    # Ceremony contribution
    class CeremonyContribution
      attr_reader :contributor_id, :hash, :timestamp, :verified

      def initialize(contributor_id:, hash:, timestamp:, verified:)
        @contributor_id = contributor_id
        @hash = hash
        @timestamp = timestamp
        @verified = verified
      end

      def self.from_json(json)
        CeremonyContribution.new(
          contributor_id: json['contributor_id'],
          hash: json['hash'],
          timestamp: json['timestamp'] || 0,
          verified: json['verified'] || false
        )
      end
    end

    # Trusted setup ceremony
    class TrustedSetup
      attr_reader :id, :circuit_type, :system, :contribution_count,
                  :latest_hash, :complete, :contributions

      def initialize(id:, circuit_type:, system:, contribution_count:,
                     latest_hash:, complete:, contributions:)
        @id = id
        @circuit_type = circuit_type
        @system = system
        @contribution_count = contribution_count
        @latest_hash = latest_hash
        @complete = complete
        @contributions = contributions
      end

      def self.from_json(json)
        TrustedSetup.new(
          id: json['id'],
          circuit_type: json['circuit_type'],
          system: json['system'],
          contribution_count: json['contribution_count'] || 0,
          latest_hash: json['latest_hash'] || '',
          complete: json['complete'] || false,
          contributions: (json['contributions'] || []).map { |c| CeremonyContribution.from_json(c) }
        )
      end
    end

    # ZK SDK configuration
    class Config
      attr_accessor :api_key, :endpoint, :ws_endpoint, :timeout, :retries,
                    :default_proof_system, :debug

      def initialize(
        api_key:,
        endpoint: 'https://zk.synor.io/v1',
        ws_endpoint: 'wss://zk.synor.io/v1/ws',
        timeout: 60,
        retries: 3,
        default_proof_system: ProofSystem::GROTH16,
        debug: false
      )
        @api_key = api_key
        @endpoint = endpoint
        @ws_endpoint = ws_endpoint
        @timeout = timeout
        @retries = retries
        @default_proof_system = default_proof_system
        @debug = debug
      end
    end

    # ZK exception
    class ZkError < StandardError
      attr_reader :code, :status

      def initialize(message, code: nil, status: nil)
        super(message)
        @code = code
        @status = status
      end
    end

    # Proof system info
    ProofSystemInfo = Struct.new(:name, :proof_size, :verification_time_ms, :trusted_setup, :universal)

    PROOF_SYSTEM_INFO = {
      ProofSystem::GROTH16 => ProofSystemInfo.new('Groth16', 192, 10, true, false),
      ProofSystem::PLONK => ProofSystemInfo.new('PLONK', 512, 15, true, true),
      ProofSystem::STARK => ProofSystemInfo.new('STARK', 50_000, 30, false, true)
    }.freeze

    # Constants
    MAX_BATCH_SIZE = 1000
    STATE_TREE_DEPTH = 32
    PROOF_EXPIRY_BLOCKS = 100

    # Main ZK client
    class Client
      attr_reader :proofs, :circuits, :rollup, :state, :ceremony

      def initialize(config)
        @config = config
        @closed = false

        @proofs = ProofsClient.new(self)
        @circuits = CircuitsClient.new(self)
        @rollup = RollupClient.new(self)
        @state = StateClient.new(self)
        @ceremony = CeremonyClient.new(self)
      end

      def default_proof_system
        @config.default_proof_system
      end

      def health_check
        result = get('/health')
        result['status'] == 'healthy'
      rescue StandardError
        false
      end

      def get_info
        get('/info')
      end

      def close
        @closed = true
      end

      def closed?
        @closed
      end

      def get(path)
        request(:get, path)
      end

      def post(path, body)
        request(:post, path, body)
      end

      private

      def request(method, path, body = nil)
        raise ZkError.new('Client has been closed', code: 'CLIENT_CLOSED') if @closed

        uri = URI.parse("#{@config.endpoint}#{path}")

        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = uri.scheme == 'https'
        http.read_timeout = @config.timeout

        request = case method
                  when :get
                    Net::HTTP::Get.new(uri.request_uri)
                  when :post
                    req = Net::HTTP::Post.new(uri.request_uri)
                    req.body = body.to_json if body
                    req
                  end

        request['Content-Type'] = 'application/json'
        request['Authorization'] = "Bearer #{@config.api_key}"
        request['X-SDK-Version'] = 'ruby/0.1.0'

        response = http.request(request)

        unless response.is_a?(Net::HTTPSuccess)
          error = JSON.parse(response.body) rescue {}
          raise ZkError.new(
            error['message'] || "HTTP #{response.code}",
            code: error['code'],
            status: response.code.to_i
          )
        end

        JSON.parse(response.body)
      end
    end

    # Proofs sub-client
    class ProofsClient
      def initialize(zk)
        @zk = zk
      end

      def generate(request)
        body = request.to_json
        body['system'] ||= @zk.default_proof_system
        result = @zk.post('/proofs/generate', body)
        Proof.from_json(result)
      end

      def verify(proof)
        result = @zk.post('/proofs/verify', {
          'proof_id' => proof.id,
          'data' => proof.data,
          'public_inputs' => proof.public_inputs,
          'system' => proof.system,
          'circuit_type' => proof.circuit_type
        })
        VerificationResult.from_json(result)
      end

      def get(proof_id)
        result = @zk.get("/proofs/#{proof_id}")
        Proof.from_json(result)
      end

      def list(limit: nil, offset: nil)
        params = []
        params << "limit=#{limit}" if limit
        params << "offset=#{offset}" if offset
        path = params.empty? ? '/proofs' : "/proofs?#{params.join('&')}"
        result = @zk.get(path)
        (result['proofs'] || []).map { |p| Proof.from_json(p) }
      end

      def serialize(proof)
        result = @zk.post('/proofs/serialize', { 'proof_id' => proof.id })
        Base64.strict_decode64(result['data'])
      end

      def deserialize(data, system)
        result = @zk.post('/proofs/deserialize', {
          'data' => Base64.strict_encode64(data),
          'system' => system
        })
        Proof.from_json(result)
      end
    end

    # Circuits sub-client
    class CircuitsClient
      def initialize(zk)
        @zk = zk
      end

      def get_verification_key(circuit_type, system = nil)
        sys = system || @zk.default_proof_system
        result = @zk.get("/circuits/#{circuit_type}/vk?system=#{sys}")
        VerificationKey.from_json(result)
      end

      def get_proving_key(circuit_type, system = nil)
        sys = system || @zk.default_proof_system
        result = @zk.get("/circuits/#{circuit_type}/pk?system=#{sys}")
        ProvingKey.from_json(result)
      end

      def list
        result = @zk.get('/circuits')
        (result['circuits'] || []).map { |c| CircuitConfig.from_json(c) }
      end

      def get_config(circuit_type)
        result = @zk.get("/circuits/#{circuit_type}/config")
        CircuitConfig.from_json(result)
      end

      def compile(config)
        result = @zk.post('/circuits/compile', config.to_json)
        result['circuit_id']
      end
    end

    # Rollup sub-client
    class RollupClient
      def initialize(zk)
        @zk = zk
      end

      def get_stats
        result = @zk.get('/rollup/stats')
        RollupStats.from_json(result)
      end

      def get_current_batch
        result = @zk.get('/rollup/batch/current')
        Batch.from_json(result)
      end

      def get_batch(batch_number)
        result = @zk.get("/rollup/batch/#{batch_number}")
        Batch.from_json(result)
      end

      def submit_transfer(transfer)
        result = @zk.post('/rollup/transfer', transfer.to_json)
        result['tx_id']
      end

      def submit_deposit(deposit)
        result = @zk.post('/rollup/deposit', deposit.to_json)
        result['tx_id']
      end

      def submit_withdrawal(withdrawal)
        result = @zk.post('/rollup/withdraw', withdrawal.to_json)
        result['tx_id']
      end

      def finalize_batch
        result = @zk.post('/rollup/batch/finalize', {})
        Batch.from_json(result)
      end

      def get_pending_transactions
        result = @zk.get('/rollup/pending')
        (result['transactions'] || []).map { |t| Transfer.from_json(t) }
      end
    end

    # State sub-client
    class StateClient
      def initialize(zk)
        @zk = zk
      end

      def get_root
        result = @zk.get('/state/root')
        result['root']
      end

      def get_account(address)
        result = @zk.get("/state/account/#{address}")
        AccountState.from_json(result)
      end

      def get_merkle_proof(address)
        result = @zk.get("/state/proof/#{address}")
        MerkleProof.from_json(result)
      end

      def verify_merkle_proof(proof)
        result = @zk.post('/state/proof/verify', proof.to_json)
        result['valid']
      end

      def get_state_at_batch(batch_number)
        @zk.get("/state/batch/#{batch_number}")
      end
    end

    # Ceremony sub-client
    class CeremonyClient
      def initialize(zk)
        @zk = zk
      end

      def get_status(circuit_type, system = nil)
        sys = system || @zk.default_proof_system
        result = @zk.get("/ceremony/#{circuit_type}?system=#{sys}")
        TrustedSetup.from_json(result)
      end

      def contribute(circuit_type, entropy, system = nil)
        sys = system || @zk.default_proof_system
        result = @zk.post('/ceremony/contribute', {
          'circuit_type' => circuit_type,
          'entropy' => Base64.strict_encode64(entropy.pack('C*')),
          'system' => sys
        })
        CeremonyContribution.from_json(result)
      end

      def verify_contribution(circuit_type, contribution_hash)
        result = @zk.post('/ceremony/verify', {
          'circuit_type' => circuit_type,
          'contribution_hash' => contribution_hash
        })
        result['valid']
      end

      def list_contributions(circuit_type)
        result = @zk.get("/ceremony/#{circuit_type}/contributions")
        (result['contributions'] || []).map { |c| CeremonyContribution.from_json(c) }
      end
    end
  end
end
