# frozen_string_literal: true

require 'base64'

module Synor
  module Crypto
    # ============================================================================
    # Constants
    # ============================================================================

    ED25519_PUBLIC_KEY_SIZE = 32
    ED25519_SECRET_KEY_SIZE = 32
    ED25519_SIGNATURE_SIZE = 64
    DILITHIUM3_PUBLIC_KEY_SIZE = 1952
    DILITHIUM3_SIGNATURE_SIZE = 3293
    HYBRID_SIGNATURE_SIZE = 64 + 3293
    COIN_TYPE = 0x5359
    MIN_PBKDF2_ITERATIONS = 10_000
    MIN_SALT_LENGTH = 8
    DEFAULT_ENDPOINT = 'https://crypto.synor.io/v1'

    # ============================================================================
    # Enumerations
    # ============================================================================

    module Network
      MAINNET = 'mainnet'
      TESTNET = 'testnet'
      DEVNET = 'devnet'

      def self.all
        [MAINNET, TESTNET, DEVNET]
      end
    end

    module FalconVariant
      FALCON512 = 'falcon512'
      FALCON1024 = 'falcon1024'

      SIGNATURE_SIZES = {
        FALCON512 => 690,
        FALCON1024 => 1330
      }.freeze

      PUBLIC_KEY_SIZES = {
        FALCON512 => 897,
        FALCON1024 => 1793
      }.freeze

      SECURITY_LEVELS = {
        FALCON512 => 128,
        FALCON1024 => 256
      }.freeze

      def self.signature_size(variant)
        SIGNATURE_SIZES[variant] || 690
      end

      def self.public_key_size(variant)
        PUBLIC_KEY_SIZES[variant] || 897
      end

      def self.security_level(variant)
        SECURITY_LEVELS[variant] || 128
      end
    end

    module SphincsVariant
      SHAKE128S = 'shake128s'
      SHAKE192S = 'shake192s'
      SHAKE256S = 'shake256s'

      SIGNATURE_SIZES = {
        SHAKE128S => 7856,
        SHAKE192S => 16_224,
        SHAKE256S => 29_792
      }.freeze

      SECURITY_LEVELS = {
        SHAKE128S => 128,
        SHAKE192S => 192,
        SHAKE256S => 256
      }.freeze

      def self.signature_size(variant)
        SIGNATURE_SIZES[variant] || 7856
      end

      def self.security_level(variant)
        SECURITY_LEVELS[variant] || 128
      end
    end

    module PqAlgorithm
      DILITHIUM3 = 'dilithium3'
      FALCON512 = 'falcon512'
      FALCON1024 = 'falcon1024'
      SPHINCS128S = 'sphincs128s'
      SPHINCS192S = 'sphincs192s'
      SPHINCS256S = 'sphincs256s'
    end

    module AlgorithmFamily
      CLASSICAL = 'classical'
      LATTICE = 'lattice'
      HASH_BASED = 'hash_based'
      HYBRID = 'hybrid'
    end

    # ============================================================================
    # Configuration Types
    # ============================================================================

    # Configuration for the Crypto SDK.
    class CryptoConfig
      attr_accessor :api_key, :endpoint, :timeout, :retries, :debug, :default_network

      def initialize(api_key:, endpoint: DEFAULT_ENDPOINT, timeout: 30_000, retries: 3, debug: false, default_network: Network::MAINNET)
        @api_key = api_key
        @endpoint = endpoint
        @timeout = timeout
        @retries = retries
        @debug = debug
        @default_network = default_network
      end
    end

    # Configuration for key derivation.
    class DerivationConfig
      attr_accessor :salt, :info, :output_length

      def initialize(salt: nil, info: nil, output_length: 32)
        @salt = salt
        @info = info
        @output_length = output_length
      end
    end

    # Configuration for password-based key derivation.
    class PasswordDerivationConfig
      attr_accessor :salt, :iterations, :output_length

      def initialize(salt:, iterations: 100_000, output_length: 32)
        @salt = salt
        @iterations = iterations
        @output_length = output_length
      end
    end

    # BIP-44 derivation path.
    class DerivationPath
      COIN_TYPE = 0x5359

      attr_reader :account, :change, :index

      def initialize(account:, change:, index:)
        @account = account
        @change = change
        @index = index
      end

      def self.external(account:, index:)
        new(account: account, change: 0, index: index)
      end

      def self.internal(account:, index:)
        new(account: account, change: 1, index: index)
      end

      def to_s
        "m/44'/#{COIN_TYPE}'/#{account}'/#{change}/#{index}"
      end

      def to_h
        { account: account, change: change, index: index }
      end
    end

    # ============================================================================
    # Key Types
    # ============================================================================

    # Hybrid public key combining Ed25519 and Dilithium3.
    class HybridPublicKey
      attr_accessor :ed25519, :dilithium

      def initialize(ed25519: nil, dilithium: nil)
        @ed25519 = ed25519
        @dilithium = dilithium
      end

      def ed25519_bytes
        Base64.strict_decode64(@ed25519 || '')
      end

      def dilithium_bytes
        Base64.strict_decode64(@dilithium || '')
      end

      def size
        ed25519_bytes.bytesize + dilithium_bytes.bytesize
      end

      def to_h
        { 'ed25519' => ed25519, 'dilithium' => dilithium }
      end

      def self.from_hash(hash)
        new(ed25519: hash['ed25519'], dilithium: hash['dilithium'])
      end
    end

    # Secret key for hybrid signatures.
    class SecretKey
      attr_accessor :ed25519_seed, :master_seed

      def initialize(ed25519_seed: nil, master_seed: nil)
        @ed25519_seed = ed25519_seed
        @master_seed = master_seed
      end

      def ed25519_seed_bytes
        Base64.strict_decode64(@ed25519_seed || '')
      end

      def master_seed_bytes
        Base64.strict_decode64(@master_seed || '')
      end

      def to_h
        { 'ed25519_seed' => ed25519_seed, 'master_seed' => master_seed }
      end

      def self.from_hash(hash)
        new(ed25519_seed: hash['ed25519_seed'], master_seed: hash['master_seed'])
      end
    end

    # Falcon public key.
    class FalconPublicKey
      attr_accessor :variant, :bytes

      def initialize(variant: nil, bytes: nil)
        @variant = variant
        @bytes = bytes
      end

      def key_bytes
        Base64.strict_decode64(@bytes || '')
      end

      def self.from_hash(hash)
        new(variant: hash['variant'], bytes: hash['bytes'])
      end
    end

    # Falcon secret key.
    class FalconSecretKey
      attr_accessor :variant, :bytes

      def initialize(variant: nil, bytes: nil)
        @variant = variant
        @bytes = bytes
      end

      def key_bytes
        Base64.strict_decode64(@bytes || '')
      end

      def self.from_hash(hash)
        new(variant: hash['variant'], bytes: hash['bytes'])
      end
    end

    # SPHINCS+ public key.
    class SphincsPublicKey
      attr_accessor :variant, :bytes

      def initialize(variant: nil, bytes: nil)
        @variant = variant
        @bytes = bytes
      end

      def key_bytes
        Base64.strict_decode64(@bytes || '')
      end

      def self.from_hash(hash)
        new(variant: hash['variant'], bytes: hash['bytes'])
      end
    end

    # SPHINCS+ secret key.
    class SphincsSecretKey
      attr_accessor :variant, :bytes

      def initialize(variant: nil, bytes: nil)
        @variant = variant
        @bytes = bytes
      end

      def key_bytes
        Base64.strict_decode64(@bytes || '')
      end

      def self.from_hash(hash)
        new(variant: hash['variant'], bytes: hash['bytes'])
      end
    end

    # ============================================================================
    # Signature Types
    # ============================================================================

    # Hybrid signature combining Ed25519 and Dilithium3.
    class HybridSignature
      attr_accessor :ed25519, :dilithium

      def initialize(ed25519: nil, dilithium: nil)
        @ed25519 = ed25519
        @dilithium = dilithium
      end

      def ed25519_bytes
        Base64.strict_decode64(@ed25519 || '')
      end

      def dilithium_bytes
        Base64.strict_decode64(@dilithium || '')
      end

      def size
        ed25519_bytes.bytesize + dilithium_bytes.bytesize
      end

      def to_bytes
        ed25519_bytes + dilithium_bytes
      end

      def to_h
        { 'ed25519' => ed25519, 'dilithium' => dilithium }
      end

      def self.from_hash(hash)
        new(ed25519: hash['ed25519'], dilithium: hash['dilithium'])
      end
    end

    # Falcon signature.
    class FalconSignature
      attr_accessor :variant, :signature

      def initialize(variant: nil, signature: nil)
        @variant = variant
        @signature = signature
      end

      def signature_bytes
        Base64.strict_decode64(@signature || '')
      end

      def size
        signature_bytes.bytesize
      end

      def self.from_hash(hash)
        new(variant: hash['variant'], signature: hash['signature'])
      end
    end

    # SPHINCS+ signature.
    class SphincsSignature
      attr_accessor :variant, :signature

      def initialize(variant: nil, signature: nil)
        @variant = variant
        @signature = signature
      end

      def signature_bytes
        Base64.strict_decode64(@signature || '')
      end

      def size
        signature_bytes.bytesize
      end

      def self.from_hash(hash)
        new(variant: hash['variant'], signature: hash['signature'])
      end
    end

    # ============================================================================
    # Keypair Types
    # ============================================================================

    # Hybrid keypair for Ed25519 + Dilithium3.
    class HybridKeypair
      attr_accessor :public_key, :secret_key, :addresses

      def initialize(public_key: nil, secret_key: nil, addresses: {})
        @public_key = public_key
        @secret_key = secret_key
        @addresses = addresses
      end

      def get_address(network)
        addresses[network] || ''
      end

      def self.from_hash(hash)
        new(
          public_key: HybridPublicKey.from_hash(hash['public_key'] || {}),
          secret_key: SecretKey.from_hash(hash['secret_key'] || {}),
          addresses: hash['addresses'] || {}
        )
      end
    end

    # Falcon keypair.
    class FalconKeypair
      attr_accessor :variant, :public_key, :secret_key

      def initialize(variant: nil, public_key: nil, secret_key: nil)
        @variant = variant
        @public_key = public_key
        @secret_key = secret_key
      end

      def self.from_hash(hash)
        new(
          variant: hash['variant'],
          public_key: FalconPublicKey.from_hash(hash['public_key'] || {}),
          secret_key: FalconSecretKey.from_hash(hash['secret_key'] || {})
        )
      end
    end

    # SPHINCS+ keypair.
    class SphincsKeypair
      attr_accessor :variant, :public_key, :secret_key

      def initialize(variant: nil, public_key: nil, secret_key: nil)
        @variant = variant
        @public_key = public_key
        @secret_key = secret_key
      end

      def self.from_hash(hash)
        new(
          variant: hash['variant'],
          public_key: SphincsPublicKey.from_hash(hash['public_key'] || {}),
          secret_key: SphincsSecretKey.from_hash(hash['secret_key'] || {})
        )
      end
    end

    # ============================================================================
    # Mnemonic Types
    # ============================================================================

    # BIP-39 mnemonic phrase.
    class Mnemonic
      attr_accessor :phrase, :words, :word_count, :entropy

      def initialize(phrase: nil, words: nil, word_count: 0, entropy: nil)
        @phrase = phrase
        @words = words
        @word_count = word_count
        @entropy = entropy
      end

      def get_words
        words || phrase&.split(' ') || []
      end

      def get_word_count
        word_count.positive? ? word_count : get_words.size
      end

      def entropy_bytes
        entropy ? Base64.strict_decode64(entropy) : ''
      end

      def self.from_hash(hash)
        new(
          phrase: hash['phrase'],
          words: hash['words'],
          word_count: hash['word_count'] || 0,
          entropy: hash['entropy']
        )
      end
    end

    # Mnemonic validation result.
    class MnemonicValidation
      attr_accessor :valid, :error

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

      def self.from_hash(hash)
        new(valid: hash['valid'], error: hash['error'])
      end
    end

    # ============================================================================
    # Address Types
    # ============================================================================

    # Blockchain address.
    class Address
      attr_accessor :address, :network, :pubkey_hash

      def initialize(address: nil, network: nil, pubkey_hash: nil)
        @address = address
        @network = network
        @pubkey_hash = pubkey_hash
      end

      def pubkey_hash_bytes
        pubkey_hash ? Base64.strict_decode64(pubkey_hash) : ''
      end

      def self.from_hash(hash)
        new(
          address: hash['address'],
          network: hash['network'],
          pubkey_hash: hash['pubkey_hash']
        )
      end
    end

    # ============================================================================
    # Hash Types
    # ============================================================================

    # 256-bit hash result.
    class Hash256
      attr_accessor :hash

      def initialize(hash: nil)
        @hash = hash
      end

      def hex
        hash
      end

      def bytes
        [hash].pack('H*')
      end

      def self.from_hash(hash_data)
        new(hash: hash_data['hash'])
      end
    end

    # ============================================================================
    # Error Types
    # ============================================================================

    # Crypto SDK exception.
    class CryptoError < StandardError
      attr_reader :code, :http_status

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