/**
 * Synor Crypto SDK for JavaScript/TypeScript
 *
 * Quantum-resistant cryptographic primitives for the Synor blockchain.
 *
 * Features:
 * - Hybrid Ed25519 + Dilithium3 signatures (quantum-resistant)
 * - BIP-39 mnemonic support (12-24 words)
 * - BIP-44 hierarchical key derivation
 * - Post-quantum algorithms: Falcon, SPHINCS+
 * - Secure key derivation (HKDF, PBKDF2)
 *
 * @example
 * ```typescript
 * import { SynorCrypto } from '@synor/crypto';
 *
 * const crypto = new SynorCrypto({ apiKey: 'your-api-key' });
 *
 * // Generate a mnemonic
 * const mnemonic = await crypto.mnemonic.generate(24);
 * console.log('Backup words:', mnemonic.phrase);
 *
 * // Create keypair from mnemonic
 * const keypair = await crypto.keypairs.fromMnemonic(mnemonic);
 * const address = keypair.address('mainnet');
 *
 * // Sign a message
 * const signature = await crypto.signing.sign(keypair, Buffer.from('Hello!'));
 * ```
 */

import type {
  CryptoConfig,
  Network,
  MnemonicWordCount,
  Mnemonic,
  MnemonicValidation,
  HybridKeypair,
  HybridPublicKey,
  HybridSignature,
  FalconVariant,
  FalconKeypair,
  FalconSignature,
  SphincsVariant,
  SphincsKeypair,
  SphincsSignature,
  DerivationPath,
  DerivationConfig,
  PasswordDerivationConfig,
  AlgorithmCapabilities,
  NegotiationPolicy,
  NegotiationResult,
  SessionParams,
  Hash256,
  Address,
} from './types';

export * from './types';

// ============================================================================
// Mnemonic Client
// ============================================================================

/** Mnemonic operations */
export class MnemonicClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Generates a new random mnemonic.
   * @param wordCount Number of words (12, 15, 18, 21, or 24)
   */
  async generate(wordCount: MnemonicWordCount = 24): Promise<Mnemonic> {
    const body = { word_count: wordCount };
    return this.crypto.post<Mnemonic>('/mnemonic/generate', body);
  }

  /**
   * Creates a mnemonic from a phrase string.
   */
  async fromPhrase(phrase: string): Promise<Mnemonic> {
    return this.crypto.post<Mnemonic>('/mnemonic/from-phrase', { phrase });
  }

  /**
   * Creates a mnemonic from entropy bytes.
   */
  async fromEntropy(entropy: Uint8Array): Promise<Mnemonic> {
    const entropyBase64 = Buffer.from(entropy).toString('base64');
    return this.crypto.post<Mnemonic>('/mnemonic/from-entropy', { entropy: entropyBase64 });
  }

  /**
   * Validates a mnemonic phrase.
   */
  async validate(phrase: string): Promise<MnemonicValidation> {
    return this.crypto.post<MnemonicValidation>('/mnemonic/validate', { phrase });
  }

  /**
   * Derives a 64-byte seed from a mnemonic.
   * @param mnemonic The mnemonic phrase or object
   * @param passphrase Optional BIP-39 passphrase
   */
  async toSeed(mnemonic: Mnemonic | string, passphrase: string = ''): Promise<Uint8Array> {
    const phrase = typeof mnemonic === 'string' ? mnemonic : mnemonic.phrase;
    const result = await this.crypto.post<{ seed: string }>('/mnemonic/to-seed', {
      phrase,
      passphrase,
    });
    return Buffer.from(result.seed, 'base64');
  }

  /**
   * Suggests word completions from the BIP-39 wordlist.
   */
  async suggestWords(partial: string, limit: number = 10): Promise<string[]> {
    const result = await this.crypto.post<{ suggestions: string[] }>('/mnemonic/suggest', {
      partial,
      limit,
    });
    return result.suggestions;
  }
}

// ============================================================================
// Keypair Client
// ============================================================================

/** Keypair operations */
export class KeypairClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Generates a new random hybrid keypair.
   */
  async generate(): Promise<HybridKeypair> {
    const result = await this.crypto.post<HybridKeypairResponse>('/keypair/generate');
    return this.wrapKeypair(result);
  }

  /**
   * Creates a keypair from a mnemonic.
   * @param mnemonic The mnemonic phrase or object
   * @param passphrase Optional BIP-39 passphrase
   */
  async fromMnemonic(mnemonic: Mnemonic | string, passphrase: string = ''): Promise<HybridKeypair> {
    const phrase = typeof mnemonic === 'string' ? mnemonic : mnemonic.phrase;
    const result = await this.crypto.post<HybridKeypairResponse>('/keypair/from-mnemonic', {
      phrase,
      passphrase,
    });
    return this.wrapKeypair(result);
  }

  /**
   * Creates a keypair from a 64-byte seed.
   */
  async fromSeed(seed: Uint8Array): Promise<HybridKeypair> {
    const seedBase64 = Buffer.from(seed).toString('base64');
    const result = await this.crypto.post<HybridKeypairResponse>('/keypair/from-seed', {
      seed: seedBase64,
    });
    return this.wrapKeypair(result);
  }

  /**
   * Derives a child keypair using BIP-44 path.
   */
  async derive(
    parentKeypair: HybridKeypair,
    path: DerivationPath
  ): Promise<HybridKeypair> {
    const result = await this.crypto.post<HybridKeypairResponse>('/keypair/derive', {
      public_key: this.encodePublicKey(parentKeypair.publicKey),
      path,
    });
    return this.wrapKeypair(result);
  }

  /**
   * Gets the address for a public key.
   */
  async getAddress(publicKey: HybridPublicKey, network: Network): Promise<Address> {
    return this.crypto.post<Address>('/keypair/address', {
      public_key: this.encodePublicKey(publicKey),
      network,
    });
  }

  private wrapKeypair(response: HybridKeypairResponse): HybridKeypair {
    const publicKey: HybridPublicKey = {
      ed25519: Buffer.from(response.public_key.ed25519, 'base64'),
      dilithium: Buffer.from(response.public_key.dilithium, 'base64'),
    };

    return {
      publicKey,
      secretKey: {
        ed25519Seed: Buffer.from(response.secret_key.ed25519_seed, 'base64'),
        masterSeed: Buffer.from(response.secret_key.master_seed, 'base64'),
      },
      address: (network: Network) => response.addresses[network] || '',
    };
  }

  private encodePublicKey(pk: HybridPublicKey): object {
    return {
      ed25519: Buffer.from(pk.ed25519).toString('base64'),
      dilithium: Buffer.from(pk.dilithium).toString('base64'),
    };
  }
}

interface HybridKeypairResponse {
  public_key: {
    ed25519: string;
    dilithium: string;
  };
  secret_key: {
    ed25519_seed: string;
    master_seed: string;
  };
  addresses: Record<Network, string>;
}

// ============================================================================
// Signing Client
// ============================================================================

/** Signing and verification operations */
export class SigningClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Signs a message with a hybrid keypair.
   */
  async sign(keypair: HybridKeypair, message: Uint8Array): Promise<HybridSignature> {
    const result = await this.crypto.post<{ ed25519: string; dilithium: string }>(
      '/sign/hybrid',
      {
        secret_key: {
          ed25519_seed: Buffer.from(keypair.secretKey.ed25519Seed).toString('base64'),
          master_seed: Buffer.from(keypair.secretKey.masterSeed).toString('base64'),
        },
        message: Buffer.from(message).toString('base64'),
      }
    );

    return {
      ed25519: Buffer.from(result.ed25519, 'base64'),
      dilithium: Buffer.from(result.dilithium, 'base64'),
    };
  }

  /**
   * Verifies a hybrid signature.
   */
  async verify(
    publicKey: HybridPublicKey,
    message: Uint8Array,
    signature: HybridSignature
  ): Promise<boolean> {
    const result = await this.crypto.post<{ valid: boolean }>('/sign/verify', {
      public_key: {
        ed25519: Buffer.from(publicKey.ed25519).toString('base64'),
        dilithium: Buffer.from(publicKey.dilithium).toString('base64'),
      },
      message: Buffer.from(message).toString('base64'),
      signature: {
        ed25519: Buffer.from(signature.ed25519).toString('base64'),
        dilithium: Buffer.from(signature.dilithium).toString('base64'),
      },
    });
    return result.valid;
  }

  /**
   * Signs a message using Ed25519 only (faster but not quantum-resistant).
   */
  async signEd25519(secretKey: Uint8Array, message: Uint8Array): Promise<Uint8Array> {
    const result = await this.crypto.post<{ signature: string }>('/sign/ed25519', {
      secret_key: Buffer.from(secretKey).toString('base64'),
      message: Buffer.from(message).toString('base64'),
    });
    return Buffer.from(result.signature, 'base64');
  }

  /**
   * Verifies an Ed25519 signature.
   */
  async verifyEd25519(
    publicKey: Uint8Array,
    message: Uint8Array,
    signature: Uint8Array
  ): Promise<boolean> {
    const result = await this.crypto.post<{ valid: boolean }>('/sign/verify-ed25519', {
      public_key: Buffer.from(publicKey).toString('base64'),
      message: Buffer.from(message).toString('base64'),
      signature: Buffer.from(signature).toString('base64'),
    });
    return result.valid;
  }
}

// ============================================================================
// Falcon Client
// ============================================================================

/** Falcon (FIPS 206) compact signature operations */
export class FalconClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Generates a Falcon keypair.
   * @param variant Falcon-512 (128-bit) or Falcon-1024 (256-bit)
   */
  async generate(variant: FalconVariant = 'falcon512'): Promise<FalconKeypair> {
    return this.crypto.post<FalconKeypair>('/falcon/generate', { variant });
  }

  /**
   * Signs a message with Falcon.
   */
  async sign(keypair: FalconKeypair, message: Uint8Array): Promise<FalconSignature> {
    const result = await this.crypto.post<{ signature: string; variant: FalconVariant }>(
      '/falcon/sign',
      {
        variant: keypair.variant,
        secret_key: Buffer.from(keypair.secretKey.bytes).toString('base64'),
        message: Buffer.from(message).toString('base64'),
      }
    );
    return {
      variant: result.variant,
      bytes: Buffer.from(result.signature, 'base64'),
    };
  }

  /**
   * Verifies a Falcon signature.
   */
  async verify(
    publicKey: Uint8Array,
    message: Uint8Array,
    signature: FalconSignature
  ): Promise<boolean> {
    const result = await this.crypto.post<{ valid: boolean }>('/falcon/verify', {
      variant: signature.variant,
      public_key: Buffer.from(publicKey).toString('base64'),
      message: Buffer.from(message).toString('base64'),
      signature: Buffer.from(signature.bytes).toString('base64'),
    });
    return result.valid;
  }

  /**
   * Returns the signature size for a variant.
   */
  signatureSize(variant: FalconVariant): number {
    return variant === 'falcon512' ? 690 : 1330;
  }

  /**
   * Returns the public key size for a variant.
   */
  publicKeySize(variant: FalconVariant): number {
    return variant === 'falcon512' ? 897 : 1793;
  }

  /**
   * Returns the security level in bits.
   */
  securityLevel(variant: FalconVariant): number {
    return variant === 'falcon512' ? 128 : 256;
  }
}

// ============================================================================
// SPHINCS+ Client
// ============================================================================

/** SPHINCS+ (FIPS 205) hash-based signature operations */
export class SphincsClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Generates a SPHINCS+ keypair.
   */
  async generate(variant: SphincsVariant = 'shake128s'): Promise<SphincsKeypair> {
    return this.crypto.post<SphincsKeypair>('/sphincs/generate', { variant });
  }

  /**
   * Signs a message with SPHINCS+.
   */
  async sign(keypair: SphincsKeypair, message: Uint8Array): Promise<SphincsSignature> {
    const result = await this.crypto.post<{ signature: string; variant: SphincsVariant }>(
      '/sphincs/sign',
      {
        variant: keypair.variant,
        secret_key: Buffer.from(keypair.secretKey.bytes).toString('base64'),
        message: Buffer.from(message).toString('base64'),
      }
    );
    return {
      variant: result.variant,
      bytes: Buffer.from(result.signature, 'base64'),
    };
  }

  /**
   * Verifies a SPHINCS+ signature.
   */
  async verify(
    publicKey: Uint8Array,
    message: Uint8Array,
    signature: SphincsSignature
  ): Promise<boolean> {
    const result = await this.crypto.post<{ valid: boolean }>('/sphincs/verify', {
      variant: signature.variant,
      public_key: Buffer.from(publicKey).toString('base64'),
      message: Buffer.from(message).toString('base64'),
      signature: Buffer.from(signature.bytes).toString('base64'),
    });
    return result.valid;
  }

  /**
   * Returns the signature size for a variant.
   */
  signatureSize(variant: SphincsVariant): number {
    const sizes: Record<SphincsVariant, number> = {
      shake128s: 7856,
      shake192s: 16224,
      shake256s: 29792,
    };
    return sizes[variant];
  }

  /**
   * Returns the security level in bits.
   */
  securityLevel(variant: SphincsVariant): number {
    const levels: Record<SphincsVariant, number> = {
      shake128s: 128,
      shake192s: 192,
      shake256s: 256,
    };
    return levels[variant];
  }
}

// ============================================================================
// Key Derivation Client
// ============================================================================

/** Key derivation operations */
export class KdfClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Derives a key using HKDF-SHA3-256.
   */
  async deriveKey(
    seed: Uint8Array,
    config: DerivationConfig = {}
  ): Promise<Uint8Array> {
    const result = await this.crypto.post<{ key: string }>('/kdf/hkdf', {
      seed: Buffer.from(seed).toString('base64'),
      salt: config.salt ? Buffer.from(config.salt).toString('base64') : undefined,
      info: config.info ? Buffer.from(config.info).toString('base64') : undefined,
      output_length: config.outputLength ?? 32,
    });
    return Buffer.from(result.key, 'base64');
  }

  /**
   * Derives a key from a password using PBKDF2.
   */
  async deriveFromPassword(
    password: string | Uint8Array,
    config: PasswordDerivationConfig
  ): Promise<Uint8Array> {
    const passwordBytes = typeof password === 'string'
      ? Buffer.from(password, 'utf-8')
      : password;

    const result = await this.crypto.post<{ key: string }>('/kdf/pbkdf2', {
      password: Buffer.from(passwordBytes).toString('base64'),
      salt: Buffer.from(config.salt).toString('base64'),
      iterations: config.iterations,
      output_length: config.outputLength ?? 32,
    });
    return Buffer.from(result.key, 'base64');
  }

  /**
   * Derives an Ed25519 key from a master seed.
   */
  async deriveEd25519Key(masterSeed: Uint8Array, account: number): Promise<Uint8Array> {
    const result = await this.crypto.post<{ key: string }>('/kdf/ed25519', {
      master_seed: Buffer.from(masterSeed).toString('base64'),
      account,
    });
    return Buffer.from(result.key, 'base64');
  }

  /**
   * Derives a child key using BIP-32 style derivation.
   */
  async deriveChildKey(
    parentKey: Uint8Array,
    chainCode: Uint8Array,
    index: number
  ): Promise<{ key: Uint8Array; chainCode: Uint8Array }> {
    const result = await this.crypto.post<{ key: string; chain_code: string }>('/kdf/child', {
      parent_key: Buffer.from(parentKey).toString('base64'),
      chain_code: Buffer.from(chainCode).toString('base64'),
      index,
    });
    return {
      key: Buffer.from(result.key, 'base64'),
      chainCode: Buffer.from(result.chain_code, 'base64'),
    };
  }

  /**
   * Creates a BIP-44 derivation path.
   */
  createPath(account: number, change: number = 0, index: number = 0): DerivationPath {
    return { account, change, index };
  }

  /**
   * Formats a derivation path as a string.
   */
  formatPath(path: DerivationPath): string {
    const coinType = 0x5359; // Synor coin type
    return `m/44'/${coinType}'/${path.account}'/${path.change}/${path.index}`;
  }
}

// ============================================================================
// Hashing Client
// ============================================================================

/** Hashing operations */
export class HashClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Computes SHA3-256 hash.
   */
  async sha3_256(data: Uint8Array): Promise<Hash256> {
    const result = await this.crypto.post<{ hash: string }>('/hash/sha3-256', {
      data: Buffer.from(data).toString('base64'),
    });
    const bytes = Buffer.from(result.hash, 'hex');
    return { bytes, hex: result.hash };
  }

  /**
   * Computes BLAKE3 hash.
   */
  async blake3(data: Uint8Array): Promise<Hash256> {
    const result = await this.crypto.post<{ hash: string }>('/hash/blake3', {
      data: Buffer.from(data).toString('base64'),
    });
    const bytes = Buffer.from(result.hash, 'hex');
    return { bytes, hex: result.hash };
  }

  /**
   * Computes Keccak-256 hash (Ethereum compatible).
   */
  async keccak256(data: Uint8Array): Promise<Hash256> {
    const result = await this.crypto.post<{ hash: string }>('/hash/keccak256', {
      data: Buffer.from(data).toString('base64'),
    });
    const bytes = Buffer.from(result.hash, 'hex');
    return { bytes, hex: result.hash };
  }

  /**
   * Combines multiple hashes.
   */
  async combine(hashes: Uint8Array[]): Promise<Hash256> {
    const result = await this.crypto.post<{ hash: string }>('/hash/combine', {
      hashes: hashes.map(h => Buffer.from(h).toString('base64')),
    });
    const bytes = Buffer.from(result.hash, 'hex');
    return { bytes, hex: result.hash };
  }
}

// ============================================================================
// Negotiation Client
// ============================================================================

/** Algorithm negotiation operations */
export class NegotiationClient {
  constructor(private crypto: SynorCrypto) {}

  /**
   * Gets the local capabilities.
   */
  async getCapabilities(): Promise<AlgorithmCapabilities> {
    return this.crypto.get<AlgorithmCapabilities>('/negotiation/capabilities');
  }

  /**
   * Negotiates algorithm selection with a peer.
   */
  async negotiate(
    peerCapabilities: AlgorithmCapabilities,
    policy: NegotiationPolicy
  ): Promise<NegotiationResult> {
    return this.crypto.post<NegotiationResult>('/negotiation/negotiate', {
      peer_capabilities: peerCapabilities,
      policy,
    });
  }

  /**
   * Establishes session parameters after negotiation.
   */
  async establishSession(
    result: NegotiationResult,
    peerPublicKey: Uint8Array
  ): Promise<SessionParams> {
    return this.crypto.post<SessionParams>('/negotiation/session', {
      negotiation_result: result,
      peer_public_key: Buffer.from(peerPublicKey).toString('base64'),
    });
  }
}

// ============================================================================
// Main Client
// ============================================================================

/**
 * Synor Crypto SDK Client
 *
 * Provides quantum-resistant cryptographic operations for the Synor blockchain.
 */
export class SynorCrypto {
  private config: Required<CryptoConfig>;
  private closed = false;

  /** Mnemonic operations */
  readonly mnemonic: MnemonicClient;
  /** Keypair operations */
  readonly keypairs: KeypairClient;
  /** Signing operations */
  readonly signing: SigningClient;
  /** Falcon (compact PQ) operations */
  readonly falcon: FalconClient;
  /** SPHINCS+ (hash-based) operations */
  readonly sphincs: SphincsClient;
  /** Key derivation operations */
  readonly kdf: KdfClient;
  /** Hashing operations */
  readonly hash: HashClient;
  /** Algorithm negotiation */
  readonly negotiation: NegotiationClient;

  constructor(config: CryptoConfig) {
    this.config = {
      apiKey: config.apiKey,
      endpoint: config.endpoint ?? 'https://crypto.synor.io/v1',
      timeout: config.timeout ?? 30000,
      retries: config.retries ?? 3,
      debug: config.debug ?? false,
      defaultNetwork: config.defaultNetwork ?? 'mainnet',
    };

    this.mnemonic = new MnemonicClient(this);
    this.keypairs = new KeypairClient(this);
    this.signing = new SigningClient(this);
    this.falcon = new FalconClient(this);
    this.sphincs = new SphincsClient(this);
    this.kdf = new KdfClient(this);
    this.hash = new HashClient(this);
    this.negotiation = new NegotiationClient(this);
  }

  /** Default network */
  get defaultNetwork(): Network {
    return this.config.defaultNetwork;
  }

  /** Health check */
  async healthCheck(): Promise<boolean> {
    try {
      const result = await this.get<{ status: string }>('/health');
      return result.status === 'healthy';
    } catch {
      return false;
    }
  }

  /** Get service info */
  async getInfo(): Promise<Record<string, unknown>> {
    return this.get<Record<string, unknown>>('/info');
  }

  /** Close the client */
  close(): void {
    this.closed = true;
  }

  /** Check if client is closed */
  get isClosed(): boolean {
    return this.closed;
  }

  /** @internal */
  async get<T>(path: string): Promise<T> {
    this.checkClosed();
    const response = await fetch(`${this.config.endpoint}${path}`, {
      method: 'GET',
      headers: this.headers(),
    });
    return this.handleResponse<T>(response);
  }

  /** @internal */
  async post<T>(path: string, body?: unknown): Promise<T> {
    this.checkClosed();
    const response = await fetch(`${this.config.endpoint}${path}`, {
      method: 'POST',
      headers: this.headers(),
      body: body ? JSON.stringify(body) : undefined,
    });
    return this.handleResponse<T>(response);
  }

  private headers(): Record<string, string> {
    return {
      'Authorization': `Bearer ${this.config.apiKey}`,
      'Content-Type': 'application/json',
      'X-SDK-Version': 'js/0.1.0',
    };
  }

  private async handleResponse<T>(response: Response): Promise<T> {
    const json = await response.json();
    if (!response.ok) {
      const { CryptoError } = await import('./types');
      throw new CryptoError(
        json.message ?? `HTTP ${response.status}`,
        json.code
      );
    }
    return json as T;
  }

  private checkClosed(): void {
    if (this.closed) {
      const { CryptoError } = require('./types');
      throw new CryptoError('Client has been closed', 'NETWORK_ERROR');
    }
  }
}

export default SynorCrypto;
