import { log } from '../utils/logger';
import * as nacl from 'tweetnacl';

const ED25519_PUBLIC_KEY_SIZE = 32;
const KEY_PREVIEW_LENGTH = 50;

const PEM_HEADER = {
  BEGIN: '-----BEGIN PUBLIC KEY-----',
  END: '-----END PUBLIC KEY-----',
} as const;

type Environment = 'production' | 'staging' | 'development';

/**
 * Service for verifying Ed25519 signatures of server keys
 * Uses TweetNaCl for cross-browser compatibility
 */
export class SignatureVerificationService {
  private static readonly TRUSTED_SIGNING_KEYS: Record<Environment, string> = {
    production:
      'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQUU5TEUwQ2t2SGRGcFRGVHZORkVLbkZqMFJZQkc1dVYrQ0F0TkJVR2ZSUHM9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=',
    staging:
      'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQUU5TEUwQ2t2SGRGcFRGVHZORkVLbkZqMFJZQkc1dVYrQ0F0TkJVR2ZSUHM9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=',
    development:
      'LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUNvd0JRWURLMlZ3QXlFQUU5TEUwQ2t2SGRGcFRGVHZORkVLbkZqMFJZQkc1dVYrQ0F0TkJVR2ZSUHM9Ci0tLS0tRU5EIFBVQkxJQyBLRVktLS0tLQo=',
  };

  /**
   * Verify Ed25519 signature of server public key using TweetNaCl
   * @param serverPublicKeyPem PEM-encoded server public key
   * @param signatureBase64 Base64-encoded signature
   * @param environment Environment (production, staging, development)
   * @returns true if signature is valid, false otherwise
   */
  static async verifyServerKeySignature(
    serverPublicKeyPem: string,
    signatureBase64: string,
    environment: Environment = 'production'
  ): Promise<boolean> {
    try {
      log.info('signature-verification', 'Starting server key signature verification (TweetNaCl)', {
        environment,
        serverKeyLength: serverPublicKeyPem.length,
        signatureLength: signatureBase64.length,
      });

      const trustedSigningKeyPem = this.TRUSTED_SIGNING_KEYS[environment];
      if (!trustedSigningKeyPem) {
        log.error('signature-verification', 'No trusted signing key found for environment', {
          environment,
        });
        return false;
      }

      const trustedSigningKey = this.extractEd25519PublicKeyFromSPKI(trustedSigningKeyPem);
      const signature = this.base64ToUint8Array(signatureBase64);

      // CRITICAL: Server sends base64(PEM), need to decode
      const serverPublicKeyPemDecoded = atob(serverPublicKeyPem);

      // IMPORTANT: Server signs PEM bytes (with headers)
      // So we use the same PEM bytes for verification (not DER)
      const serverPublicKeyBytes = new TextEncoder().encode(serverPublicKeyPemDecoded);

      log.debug('signature-verification', 'Verification data prepared', {
        trustedKeyLength: trustedSigningKey.length,
        signatureLength: signature.length,
        messageLength: serverPublicKeyBytes.length,
      });

      const isValid = nacl.sign.detached.verify(
        serverPublicKeyBytes,
        signature,
        trustedSigningKey
      );

      if (isValid) {
        log.info('signature-verification', 'Server key signature verification successful');
      } else {
        log.warn('signature-verification', 'Server key signature verification failed');
      }

      return isValid;
    } catch (error) {
      log.error('signature-verification', 'Server key signature verification error', {
        error: error instanceof Error ? error.message : String(error),
        environment,
      });
      return false;
    }
  }

  /**
   * Extract raw Ed25519 public key (32 bytes) from base64(PEM) SPKI format
   *
   * SPKI format for Ed25519:
   * - Total size: ~44 bytes
   * - Last 32 bytes: raw Ed25519 public key
   *
   * @param pemKeyBase64 Base64-encoded PEM string
   * @returns Uint8Array (32 bytes) - raw Ed25519 public key
   */
  private static extractEd25519PublicKeyFromSPKI(pemKeyBase64: string): Uint8Array {
    try {
      const pemString = atob(pemKeyBase64);

      log.debug('signature-verification', 'Decoded PEM from base64', {
        pemLength: pemString.length,
        hasPemHeaders: pemString.includes(PEM_HEADER.BEGIN),
      });

      const pemContent = pemString
        .replace(new RegExp(PEM_HEADER.BEGIN, 'g'), '')
        .replace(new RegExp(PEM_HEADER.END, 'g'), '')
        .replace(/\s/g, '');

      const spkiBytes = this.base64ToUint8Array(pemContent);

      log.debug('signature-verification', 'Extracted SPKI bytes', {
        spkiLength: spkiBytes.length,
      });

      // SPKI contains headers (~12 bytes) + raw key (32 bytes)
      if (spkiBytes.length < ED25519_PUBLIC_KEY_SIZE) {
        throw new Error(
          `SPKI too short: ${spkiBytes.length} bytes (expected >=${ED25519_PUBLIC_KEY_SIZE})`
        );
      }

      const rawPublicKey = spkiBytes.slice(spkiBytes.length - ED25519_PUBLIC_KEY_SIZE);

      log.info('signature-verification', 'Ed25519 raw public key extracted', {
        rawKeyLength: rawPublicKey.length,
      });

      return rawPublicKey;
    } catch (error) {
      log.error('signature-verification', 'Failed to extract Ed25519 public key from SPKI', {
        error: error instanceof Error ? error.message : String(error),
        keyLength: pemKeyBase64.length,
        keyPreview: pemKeyBase64.substring(0, KEY_PREVIEW_LENGTH) + '...',
      });
      throw error;
    }
  }

  /**
   * Convert base64 to Uint8Array
   */
  private static base64ToUint8Array(base64: string): Uint8Array {
    const binaryString = atob(base64);
    const bytes = new Uint8Array(binaryString.length);
    for (let i = 0; i < binaryString.length; i++) {
      bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes;
  }
}

export const signatureVerification = new SignatureVerificationService();
