/**
 * Hardware Wallet Integration for Synor Web Wallet
 *
 * Supports Ledger and Trezor hardware wallets for secure Ed25519 signing.
 *
 * ## Hybrid Signature Flow
 *
 * Since hardware wallets don't yet support Dilithium3, hybrid signatures work as:
 * 1. Ed25519 signature: Created on hardware wallet (key never leaves device)
 * 2. Dilithium3 signature: Requested from server using Ed25519 proof
 *
 * This maintains hardware wallet security for classical signatures while
 * providing post-quantum protection via server-side Dilithium.
 *
 * ## Supported Devices
 *
 * - **Ledger Nano S/X/S Plus**: Via WebHID transport
 * - **Trezor Model T/One**: Via Trezor Connect
 */

import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
import { blake3 } from '@noble/hashes/blake3';

// ==================== Types ====================

export type HardwareWalletType = 'ledger' | 'trezor';

export interface HardwareWalletInfo {
  type: HardwareWalletType;
  model: string;
  firmwareVersion?: string;
  connected: boolean;
}

export interface HardwareWalletAccount {
  path: string;
  publicKey: Uint8Array;
  address: string;
}

export interface HardwareWallet {
  type: HardwareWalletType;
  connect(): Promise<HardwareWalletInfo>;
  disconnect(): Promise<void>;
  getAccounts(count?: number): Promise<HardwareWalletAccount[]>;
  signMessage(path: string, message: Uint8Array): Promise<Uint8Array>;
  isConnected(): boolean;
}

// ==================== Ledger Implementation ====================

class LedgerWallet implements HardwareWallet {
  type: HardwareWalletType = 'ledger';
  private transport: any = null;

  async connect(): Promise<HardwareWalletInfo> {
    try {
      // Dynamic import to avoid loading until needed
      const TransportWebHID = (await import('@ledgerhq/hw-transport-webhid')).default;

      // Request permission and connect
      this.transport = await TransportWebHID.create();

      // Get app info
      const appInfo = await this.getAppInfo();

      return {
        type: 'ledger',
        model: appInfo.model || 'Ledger Device',
        firmwareVersion: appInfo.version,
        connected: true,
      };
    } catch (error) {
      throw new Error(
        `Failed to connect to Ledger: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  async disconnect(): Promise<void> {
    if (this.transport) {
      await this.transport.close();
      this.transport = null;
    }
  }

  isConnected(): boolean {
    return this.transport !== null;
  }

  private async getAppInfo(): Promise<{ model: string; version: string }> {
    // Send APDU to get app version
    // This is a generic command that most Ledger apps support
    try {
      await this.transport.send(0xb0, 0x01, 0x00, 0x00);
      // Response format varies by app, use defaults
      return {
        model: 'Ledger Nano',
        version: '1.0.0',
      };
    } catch {
      return { model: 'Ledger Nano', version: 'Unknown' };
    }
  }

  async getAccounts(count: number = 5): Promise<HardwareWalletAccount[]> {
    if (!this.transport) {
      throw new Error('Ledger not connected');
    }

    const accounts: HardwareWalletAccount[] = [];

    // ED25519 BIP32 path for Synor: m/44'/397'/account'/0/0
    // 397 is an example coin type - in production, register with SLIP-44
    const SYNOR_COIN_TYPE = 397;

    for (let i = 0; i < count; i++) {
      const path = `m/44'/${SYNOR_COIN_TYPE}'/${i}'/0/0`;

      try {
        // Get public key from Ledger
        // APDU command for ED25519 public key derivation
        const pathBuffer = this.buildPathBuffer(path);
        const response = await this.transport.send(
          0xe0, // CLA
          0x02, // INS: GET_PUBLIC_KEY
          0x00, // P1: Don't display
          0x00, // P2
          pathBuffer
        );

        // Extract public key (32 bytes for Ed25519)
        const publicKey = response.slice(1, 33);
        const address = this.publicKeyToAddress(publicKey);

        accounts.push({ path, publicKey, address });
      } catch (error) {
        console.warn(`Failed to get account ${i}:`, error);
        break;
      }
    }

    return accounts;
  }

  async signMessage(path: string, message: Uint8Array): Promise<Uint8Array> {
    if (!this.transport) {
      throw new Error('Ledger not connected');
    }

    const pathBuffer = this.buildPathBuffer(path);

    // Build sign transaction APDU
    const data = Buffer.concat([pathBuffer, Buffer.from(message)]);

    const response = await this.transport.send(
      0xe0, // CLA
      0x04, // INS: SIGN
      0x00, // P1
      0x00, // P2
      data
    );

    // Extract signature (64 bytes for Ed25519)
    return new Uint8Array(response.slice(0, 64));
  }

  private buildPathBuffer(path: string): Buffer {
    const elements = path.split('/').slice(1); // Remove 'm'
    const buffer = Buffer.alloc(1 + elements.length * 4);
    buffer.writeUInt8(elements.length, 0);

    elements.forEach((element, index) => {
      const hardened = element.endsWith("'");
      const value = parseInt(element.replace("'", ''), 10);
      buffer.writeUInt32BE(hardened ? value + 0x80000000 : value, 1 + index * 4);
    });

    return buffer;
  }

  private publicKeyToAddress(publicKey: Uint8Array): string {
    const hash = blake3(publicKey);
    const addressBytes = hash.slice(0, 20);
    return `synor:qz${bytesToHex(addressBytes)}`;
  }
}

// ==================== Trezor Implementation ====================

class TrezorWallet implements HardwareWallet {
  type: HardwareWalletType = 'trezor';
  private connected: boolean = false;
  private TrezorConnect: any = null;

  async connect(): Promise<HardwareWalletInfo> {
    try {
      // Dynamic import
      const { default: TrezorConnect } = await import('@trezor/connect-web');
      this.TrezorConnect = TrezorConnect;

      // Initialize Trezor Connect
      await TrezorConnect.init({
        manifest: {
          appName: 'Synor Wallet',
          email: 'support@synor.cc',
          appUrl: 'https://wallet.synor.cc',
        },
        lazyLoad: true,
      });

      // Get device features to verify connection
      const result = await TrezorConnect.getFeatures();

      if (!result.success) {
        throw new Error(result.payload.error || 'Failed to connect');
      }

      this.connected = true;

      return {
        type: 'trezor',
        model: result.payload.model || 'Trezor Device',
        firmwareVersion: `${result.payload.major_version}.${result.payload.minor_version}.${result.payload.patch_version}`,
        connected: true,
      };
    } catch (error) {
      throw new Error(
        `Failed to connect to Trezor: ${error instanceof Error ? error.message : 'Unknown error'}`
      );
    }
  }

  async disconnect(): Promise<void> {
    if (this.TrezorConnect) {
      await this.TrezorConnect.dispose();
    }
    this.connected = false;
    this.TrezorConnect = null;
  }

  isConnected(): boolean {
    return this.connected;
  }

  async getAccounts(count: number = 5): Promise<HardwareWalletAccount[]> {
    if (!this.TrezorConnect) {
      throw new Error('Trezor not connected');
    }

    const accounts: HardwareWalletAccount[] = [];
    const SYNOR_COIN_TYPE = 397;

    // Get multiple public keys in a batch
    const bundle = Array.from({ length: count }, (_, i) => ({
      path: `m/44'/${SYNOR_COIN_TYPE}'/${i}'/0/0`,
      showOnTrezor: false,
    }));

    const result = await this.TrezorConnect.getPublicKey({ bundle });

    if (!result.success) {
      throw new Error(result.payload.error || 'Failed to get public keys');
    }

    for (const item of result.payload) {
      const publicKey = new Uint8Array(Buffer.from(item.publicKey, 'hex'));
      const address = this.publicKeyToAddress(publicKey);
      accounts.push({
        path: item.path,
        publicKey,
        address,
      });
    }

    return accounts;
  }

  async signMessage(path: string, message: Uint8Array): Promise<Uint8Array> {
    if (!this.TrezorConnect) {
      throw new Error('Trezor not connected');
    }

    const result = await this.TrezorConnect.signMessage({
      path,
      message: bytesToHex(message),
      coin: 'SYNOR',
    });

    if (!result.success) {
      throw new Error(result.payload.error || 'Failed to sign');
    }

    return new Uint8Array(Buffer.from(result.payload.signature, 'hex'));
  }

  private publicKeyToAddress(publicKey: Uint8Array): string {
    const hash = blake3(publicKey);
    const addressBytes = hash.slice(0, 20);
    return `synor:qz${bytesToHex(addressBytes)}`;
  }
}

// ==================== Factory and Utilities ====================

/**
 * Create a hardware wallet instance.
 */
export function createHardwareWallet(type: HardwareWalletType): HardwareWallet {
  switch (type) {
    case 'ledger':
      return new LedgerWallet();
    case 'trezor':
      return new TrezorWallet();
    default:
      throw new Error(`Unsupported hardware wallet type: ${type}`);
  }
}

/**
 * Check if WebHID is supported (required for Ledger).
 */
export function isWebHIDSupported(): boolean {
  return 'hid' in navigator;
}

/**
 * Check if hardware wallets are supported in this browser.
 */
export function isHardwareWalletSupported(): boolean {
  // WebHID for Ledger, or WebUSB fallback
  // Trezor Connect works in most browsers via popup
  return isWebHIDSupported() || 'usb' in navigator;
}

/**
 * Detect connected hardware wallets.
 * Returns a list of detected wallet types.
 */
export async function detectHardwareWallets(): Promise<HardwareWalletType[]> {
  const detected: HardwareWalletType[] = [];

  // Check for Ledger via WebHID
  if (isWebHIDSupported()) {
    try {
      const devices = await (navigator as any).hid.getDevices();
      const ledgerDevices = devices.filter(
        (d: any) => d.vendorId === 0x2c97 // Ledger vendor ID
      );
      if (ledgerDevices.length > 0) {
        detected.push('ledger');
      }
    } catch {
      // Permission not granted yet
    }
  }

  // Trezor detection would require attempting connection
  // Since it uses a popup flow, we just indicate it's available
  detected.push('trezor');

  return detected;
}

// ==================== Hybrid Signature Support ====================

/**
 * Create a hybrid signature using hardware wallet for Ed25519.
 *
 * Flow:
 * 1. Sign message with hardware wallet (Ed25519)
 * 2. Request Dilithium signature from server (using Ed25519 proof)
 *
 * @param message - Message to sign
 * @param wallet - Connected hardware wallet
 * @param accountPath - BIP32 derivation path
 * @param rpcUrl - Synor node RPC URL for Dilithium signing
 */
export async function createHybridSignatureHardware(
  message: Uint8Array,
  wallet: HardwareWallet,
  accountPath: string,
  rpcUrl: string
): Promise<{ ed25519: Uint8Array; dilithium: Uint8Array }> {
  // Sign with hardware wallet (Ed25519)
  const ed25519Signature = await wallet.signMessage(accountPath, message);

  // Request Dilithium signature from server
  // The server verifies the Ed25519 signature and returns a Dilithium signature
  const response = await fetch(rpcUrl, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      jsonrpc: '2.0',
      method: 'wallet_signDilithiumForHardware',
      params: {
        message: bytesToHex(message),
        ed25519Signature: bytesToHex(ed25519Signature),
        accountPath,
      },
      id: 1,
    }),
  });

  const result = await response.json();
  if (result.error) {
    throw new Error(`Dilithium signing failed: ${result.error.message}`);
  }

  return {
    ed25519: ed25519Signature,
    dilithium: hexToBytes(result.result.signature),
  };
}
