# frozen_string_literal: true

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

require_relative 'types'

module Synor
  module Crypto
    # Synor Crypto SDK for Ruby
    #
    # Quantum-resistant cryptographic primitives for the Synor blockchain.
    #
    # @example
    #   config = Synor::Crypto::CryptoConfig.new(api_key: 'your-api-key')
    #   crypto = Synor::Crypto::SynorCrypto.new(config)
    #
    #   # Generate a mnemonic
    #   mnemonic = crypto.mnemonic.generate(24)
    #   puts "Backup words: #{mnemonic.phrase}"
    #
    #   # Create keypair from mnemonic
    #   keypair = crypto.keypairs.from_mnemonic(mnemonic.phrase, '')
    #   address = keypair.get_address(Synor::Crypto::Network::MAINNET)
    #
    #   # Sign a message
    #   signature = crypto.signing.sign(keypair, 'Hello!')
    #
    class SynorCrypto
      attr_reader :config, :mnemonic, :keypairs, :signing, :falcon, :sphincs, :kdf, :hash

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

        @mnemonic = MnemonicClient.new(self)
        @keypairs = KeypairClient.new(self)
        @signing = SigningClient.new(self)
        @falcon = FalconClient.new(self)
        @sphincs = SphincsClient.new(self)
        @kdf = KdfClient.new(self)
        @hash = HashClient.new(self)
      end

      def default_network
        config.default_network
      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

      # @api private
      def get(path)
        check_closed!
        request(:get, path)
      end

      # @api private
      def post(path, body = {})
        check_closed!
        request(:post, path, body)
      end

      private

      def request(method, path, body = nil)
        uri = URI.parse("#{config.endpoint}#{path}")
        http = Net::HTTP.new(uri.host, uri.port)
        http.use_ssl = uri.scheme == 'https'
        http.open_timeout = config.timeout / 1000.0
        http.read_timeout = config.timeout / 1000.0

        request = case method
                  when :get
                    Net::HTTP::Get.new(uri.request_uri)
                  when :post
                    req = Net::HTTP::Post.new(uri.request_uri)
                    req.body = JSON.generate(body || {})
                    req
                  end

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

        response = http.request(request)
        handle_response(response)
      end

      def handle_response(response)
        body = JSON.parse(response.body)

        unless response.is_a?(Net::HTTPSuccess)
          message = body['message'] || "HTTP #{response.code}"
          code = body['code']
          raise CryptoError.new(message, code: code, http_status: response.code.to_i)
        end

        body
      end

      def check_closed!
        raise CryptoError.new('Client has been closed', code: 'CLIENT_CLOSED') if @closed
      end
    end

    # Mnemonic sub-client
    class MnemonicClient
      def initialize(crypto)
        @crypto = crypto
      end

      def generate(word_count = 24)
        result = @crypto.post('/mnemonic/generate', { word_count: word_count })
        Mnemonic.from_hash(result)
      end

      def from_phrase(phrase)
        result = @crypto.post('/mnemonic/from-phrase', { phrase: phrase })
        Mnemonic.from_hash(result)
      end

      def validate(phrase)
        result = @crypto.post('/mnemonic/validate', { phrase: phrase })
        MnemonicValidation.from_hash(result)
      end

      def to_seed(phrase, passphrase = '')
        result = @crypto.post('/mnemonic/to-seed', { phrase: phrase, passphrase: passphrase })
        Base64.strict_decode64(result['seed'])
      end

      def suggest_words(partial, limit = 5)
        result = @crypto.post('/mnemonic/suggest', { partial: partial, limit: limit })
        result['suggestions'] || []
      end
    end

    # Keypair sub-client
    class KeypairClient
      def initialize(crypto)
        @crypto = crypto
      end

      def generate
        result = @crypto.post('/keypair/generate')
        HybridKeypair.from_hash(result)
      end

      def from_mnemonic(phrase, passphrase = '')
        result = @crypto.post('/keypair/from-mnemonic', { phrase: phrase, passphrase: passphrase })
        HybridKeypair.from_hash(result)
      end

      def from_seed(seed)
        result = @crypto.post('/keypair/from-seed', { seed: Base64.strict_encode64(seed) })
        HybridKeypair.from_hash(result)
      end

      def get_address(public_key, network)
        result = @crypto.post('/keypair/address', {
                                public_key: public_key.to_h,
                                network: network
                              })
        Address.from_hash(result)
      end
    end

    # Signing sub-client
    class SigningClient
      def initialize(crypto)
        @crypto = crypto
      end

      def sign(keypair, message)
        message_bytes = message.is_a?(String) ? message.encode('UTF-8').bytes.pack('C*') : message
        result = @crypto.post('/sign/hybrid', {
                                secret_key: keypair.secret_key.to_h,
                                message: Base64.strict_encode64(message_bytes)
                              })
        HybridSignature.from_hash(result)
      end

      def verify(public_key, message, signature)
        message_bytes = message.is_a?(String) ? message.encode('UTF-8').bytes.pack('C*') : message
        result = @crypto.post('/sign/verify', {
                                public_key: public_key.to_h,
                                message: Base64.strict_encode64(message_bytes),
                                signature: signature.to_h
                              })
        result['valid'] == true
      end

      def sign_ed25519(secret_key, message)
        result = @crypto.post('/sign/ed25519', {
                                secret_key: Base64.strict_encode64(secret_key),
                                message: Base64.strict_encode64(message)
                              })
        Base64.strict_decode64(result['signature'])
      end
    end

    # Falcon sub-client
    class FalconClient
      def initialize(crypto)
        @crypto = crypto
      end

      def generate(variant = FalconVariant::FALCON512)
        result = @crypto.post('/falcon/generate', { variant: variant })
        FalconKeypair.from_hash(result)
      end

      def sign(keypair, message)
        result = @crypto.post('/falcon/sign', {
                                variant: keypair.variant,
                                secret_key: Base64.strict_encode64(keypair.secret_key.key_bytes),
                                message: Base64.strict_encode64(message)
                              })
        FalconSignature.from_hash(result)
      end

      def verify(public_key, message, signature)
        result = @crypto.post('/falcon/verify', {
                                variant: signature.variant,
                                public_key: Base64.strict_encode64(public_key),
                                message: Base64.strict_encode64(message),
                                signature: Base64.strict_encode64(signature.signature_bytes)
                              })
        result['valid'] == true
      end
    end

    # SPHINCS+ sub-client
    class SphincsClient
      def initialize(crypto)
        @crypto = crypto
      end

      def generate(variant = SphincsVariant::SHAKE128S)
        result = @crypto.post('/sphincs/generate', { variant: variant })
        SphincsKeypair.from_hash(result)
      end

      def sign(keypair, message)
        result = @crypto.post('/sphincs/sign', {
                                variant: keypair.variant,
                                secret_key: Base64.strict_encode64(keypair.secret_key.key_bytes),
                                message: Base64.strict_encode64(message)
                              })
        SphincsSignature.from_hash(result)
      end

      def verify(public_key, message, signature)
        result = @crypto.post('/sphincs/verify', {
                                variant: signature.variant,
                                public_key: Base64.strict_encode64(public_key),
                                message: Base64.strict_encode64(message),
                                signature: Base64.strict_encode64(signature.signature_bytes)
                              })
        result['valid'] == true
      end
    end

    # KDF sub-client
    class KdfClient
      def initialize(crypto)
        @crypto = crypto
      end

      def derive_key(seed, config = DerivationConfig.new)
        body = {
          seed: Base64.strict_encode64(seed),
          output_length: config.output_length
        }
        body[:salt] = Base64.strict_encode64(config.salt) if config.salt
        body[:info] = Base64.strict_encode64(config.info) if config.info

        result = @crypto.post('/kdf/hkdf', body)
        Base64.strict_decode64(result['key'])
      end

      def derive_from_password(password, config)
        result = @crypto.post('/kdf/pbkdf2', {
                                password: Base64.strict_encode64(password),
                                salt: Base64.strict_encode64(config.salt),
                                iterations: config.iterations,
                                output_length: config.output_length
                              })
        Base64.strict_decode64(result['key'])
      end
    end

    # Hash sub-client
    class HashClient
      def initialize(crypto)
        @crypto = crypto
      end

      def sha3_256(data)
        result = @crypto.post('/hash/sha3-256', { data: Base64.strict_encode64(data) })
        Hash256.from_hash(result)
      end

      def blake3(data)
        result = @crypto.post('/hash/blake3', { data: Base64.strict_encode64(data) })
        Hash256.from_hash(result)
      end

      def keccak256(data)
        result = @crypto.post('/hash/keccak256', { data: Base64.strict_encode64(data) })
        Hash256.from_hash(result)
      end
    end
  end
end
