/**
 * DID (Decentralized Identifier) helpers
 * Enhanced implementation with proper crypto operations
 */

import fetch from 'node-fetch';
import { createVerify, createPublicKey } from 'crypto';
import { decode as multibaseDecode } from 'multibase';
import base58 from 'base58-universal';
import { importJWK } from 'jose';
import forge from 'node-forge';
import { verify as ed25519Verify } from '@noble/ed25519';

export interface DIDDocument {
  id: string;
  '@context': string[];
  verificationMethod: VerificationMethod[];
  authentication: string[];
}

export interface VerificationMethod {
  id: string;
  type: string;
  controller: string;
  publicKeyMultibase?: string;
  publicKeyJwk?: {
    kty: string;
    crv?: string;
    x?: string;
    y?: string;
    n?: string;
    e?: string;
  };
}

export class DIDResolver {
  async resolve(did: string): Promise<DIDDocument> {
    // Extract method and identifier from DID
    const didParts = did.split(':');
    if (didParts.length < 3) {
      throw new Error(`Invalid DID format: ${did}`);
    }

    const method = didParts[1];
    const identifier = didParts.slice(2).join(':');

    // Resolve based on DID method
    if (method === 'web') {
      // did:web resolution
      const url = `https://${identifier}/.well-known/did.json`;
      const response = await fetch(url);
      if (!response.ok) {
        throw new Error(`Failed to resolve DID: ${response.status}`);
      }
      return (await response.json()) as DIDDocument;
    } else if (method === 'key') {
      // did:key resolution - generate document from key
      const publicKeyMultibase: string = identifier;
      const verificationMethodObj: VerificationMethod = {
        id: `${did}#keys-1`,
        type: 'Ed25519VerificationKey2020',
        controller: did,
        publicKeyMultibase: publicKeyMultibase,
      };
      // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
      const verificationMethodArray: VerificationMethod[] = [verificationMethodObj];
      const result: DIDDocument = {
        id: did,
        '@context': ['https://www.w3.org/ns/did/v1'],
        verificationMethod: verificationMethodArray,
        authentication: [`${did}#keys-1`],
      };
      return result;
    }

    throw new Error(`Unsupported DID method: ${method}`);
  }

  /**
   * Decode multibase-encoded public key
   */
  private decodeMultibaseKey(multibaseKey: string): Buffer {
    try {
      // Use multibase library to decode
      const decoded = multibaseDecode(multibaseKey);
      return Buffer.from(decoded);
    } catch (error) {
      // Fallback: try base58 decoding for 'z' prefix (base58btc)
      if (multibaseKey.startsWith('z')) {
        try {
          const base58Encoded = multibaseKey.slice(1);
          const decoded = base58.decode(base58Encoded);
          return Buffer.from(decoded);
        } catch {
          throw new Error('Failed to decode multibase key');
        }
      }
      throw error;
    }
  }

  /**
   * Convert JWK to PEM format for RSA keys
   */
  private jwkToPEM(jwk: { n?: string; e?: string }): string {
    if (!jwk.n || !jwk.e) {
      throw new Error('Invalid RSA JWK: missing n or e');
    }

    // Convert base64url to base64
    const n = jwk.n.replace(/-/g, '+').replace(/_/g, '/');
    const e = jwk.e.replace(/-/g, '+').replace(/_/g, '/');

    // Decode to buffers and convert to BigIntegers using forge
    const modulusBuffer = Buffer.from(n, 'base64');
    const exponentBuffer = Buffer.from(e, 'base64');

    // Convert buffers to hex strings for BigInteger
    const modulusHex = modulusBuffer.toString('hex');
    const exponentHex = exponentBuffer.toString('hex');

    // Create BigIntegers from hex strings
    const modulusBigInt = new forge.jsbn.BigInteger(modulusHex, 16);
    const exponentBigInt = new forge.jsbn.BigInteger(exponentHex, 16);

    // Use node-forge to create RSA public key
    const publicKey = forge.pki.rsa.setPublicKey(modulusBigInt, exponentBigInt);

    // Convert to PEM
    return forge.pki.publicKeyToPem(publicKey);
  }

  /**
   * Verify signature with Ed25519 public key using @noble/ed25519
   */
  private async verifyEd25519(
    publicKey: Buffer,
    message: string,
    signature: string
  ): Promise<boolean> {
    try {
      // Ed25519 public keys are 32 bytes
      if (publicKey.length !== 32) {
        // Try to extract the 32-byte public key from multibase encoding
        // Multibase Ed25519 keys often have a 'z' prefix (base58btc)
        if (publicKey.length > 32) {
          // Extract the last 32 bytes (public key is typically at the end)
          publicKey = publicKey.slice(-32);
        } else {
          console.error('Invalid Ed25519 public key length:', publicKey.length);
          return false;
        }
      }

      const messageBytes = Buffer.from(message, 'utf-8');
      const signatureBytes = Buffer.from(signature, 'base64');

      // Ed25519 signatures are 64 bytes
      if (signatureBytes.length !== 64) {
        console.error('Invalid Ed25519 signature length:', signatureBytes.length);
        return false;
      }

      // Verify signature using @noble/ed25519
      // ed25519Verify is synchronous, but we keep async for consistency
      return Promise.resolve(ed25519Verify(signatureBytes, messageBytes, publicKey));
    } catch (error) {
      console.error('Ed25519 verification failed:', error);
      return false;
    }
  }

  /**
   * Verify signature with EC (secp256k1) public key
   */
  private async verifyEC(
    jwk: { crv?: string; x?: string; y?: string },
    _message: string,
    _signature: string
  ): Promise<boolean> {
    try {
      if (jwk.crv !== 'secp256k1' || !jwk.x || !jwk.y) {
        return false;
      }

      // For EC key verification, we need to use the public key properly
      // Import JWK using jose library to get the key format
      await importJWK(
        {
          kty: 'EC',
          crv: jwk.crv,
          x: jwk.x,
          y: jwk.y,
        },
        'ES256K'
      );

      // Note: EC signature verification requires proper key format
      // For production, this should use the imported key with proper signature format
      // This is a simplified implementation - full implementation would need
      // to handle JWS format or use a proper EC signature verification library
      console.warn('EC signature verification not fully implemented - using placeholder');
      return false; // Placeholder - return false for now
    } catch (error) {
      console.error('EC signature verification failed:', error);
      return false;
    }
  }

  /**
   * Verify signature with RSA public key
   */
  private verifyRSA(
    jwk: { n?: string; e?: string },
    message: string,
    signature: string
  ): boolean {
    try {
      if (!jwk.n || !jwk.e) {
        return false;
      }

      // Convert JWK to PEM
      const pemKey = this.jwkToPEM(jwk);

      // Create public key from PEM
      const publicKey = createPublicKey({
        key: pemKey,
        type: 'spki',
        format: 'pem',
      });

      // Verify signature
      const verify = createVerify('SHA256');
      verify.update(message);
      return verify.verify(publicKey, Buffer.from(signature, 'base64'));
    } catch (error) {
      console.error('RSA signature verification failed:', error);
      return false;
    }
  }

  async verifySignature(
    did: string,
    message: string,
    signature: string
  ): Promise<boolean> {
    try {
      const document = await this.resolve(did);
      const verificationMethod = document.verificationMethod[0];
      if (!verificationMethod) {
        return false;
      }

      // Handle multibase-encoded public keys (e.g., Ed25519)
      if (verificationMethod.publicKeyMultibase) {
        try {
          const publicKey = this.decodeMultibaseKey(verificationMethod.publicKeyMultibase);
          return await this.verifyEd25519(publicKey, message, signature);
        } catch (error) {
          console.error('Multibase key verification failed:', error);
          return false;
        }
      }

      // Handle JWK format
      if (verificationMethod.publicKeyJwk) {
        const jwk = verificationMethod.publicKeyJwk;

        // EC keys (secp256k1, P-256, etc.)
        if (jwk.kty === 'EC') {
          return await this.verifyEC(jwk, message, signature);
        }

        // RSA keys
        if (jwk.kty === 'RSA') {
          return this.verifyRSA(jwk, message, signature);
        }

        // Ed25519 in JWK format (less common)
        if (jwk.kty === 'OKP' && jwk.crv === 'Ed25519' && jwk.x) {
          try {
            const publicKey = Buffer.from(jwk.x, 'base64url');
            return await this.verifyEd25519(publicKey, message, signature);
          } catch (error) {
            console.error('Ed25519 JWK verification failed:', error);
            return false;
          }
        }
      }

      return false;
    } catch (error) {
      console.error('DID signature verification failed:', error);
      return false;
    }
  }
}

