/**
 * Certificate Pinning Service for MITM attack protection
 */

import { API_CONFIG } from '../config/api';

export interface CertificateInfo {
  fingerprint: string;
  algorithm: string;
  issuer: string;
  subject: string;
  validFrom: string;
  validTo: string;
}

export interface PinnedCertificate {
  hostname: string;
  fingerprint: string;
  algorithm: string;
  lastVerified: string;
}

interface CertificatePinConfig {
  pins: Record<string, string[]>;
  backup_pins: Record<string, string[]>;
  rotation_schedule: {
    primary_pin_ttl_days: number;
    backup_pin_ttl_days: number;
    next_rotation: string;
  };
  fallback_enabled: boolean;
  version: string;
}

interface PinCache {
  config: CertificatePinConfig;
  lastUpdated: number;
  ttl: number;
}

const MILLISECONDS_PER_DAY = 1000 * 60 * 60 * 24;

const CERTIFICATE_CONSTANTS = {
  CACHE_TTL: 24 * 60 * 60 * 1000, // 24 hours
  MAX_AGE_DAYS: 365,
  HTTPS_PROTOCOL: 'https:',
} as const;

const HASH_ALGORITHM = {
  SHA256: 'sha256',
  SHA1: 'sha1',
} as const;

const HEX_FORMAT = {
  RADIX: 16,
  PAD_LENGTH: 2,
  PAD_CHAR: '0',
} as const;

export class CertificatePinningService {
  public static pinCache: PinCache | null = null;
  public static readonly CACHE_TTL = CERTIFICATE_CONSTANTS.CACHE_TTL;

  /**
   * INSTRUCTIONS FOR OBTAINING CERTIFICATE FINGERPRINT:
   *
   * 1. To get SHA-256 fingerprint of certificate, run:
   *    openssl s_client -connect DOMAIN:443 -servername DOMAIN | openssl x509 -pubkey -noout | openssl pkey -pubin -outform der | openssl dgst -sha256 -binary | openssl enc -base64
   *
   * 2. Alternative method via curl:
   *    curl -sI https://DOMAIN | openssl s_client -connect DOMAIN:443 -servername DOMAIN | openssl x509 -fingerprint -sha256 -noout
   *
   * 3. For browser, use Developer Tools > Security > Certificate Details
   *
   * IMPORTANT: Replace PLACEHOLDER values with real fingerprints before production deployment!
   */
  private static readonly PINNED_CERTIFICATES: PinnedCertificate[] = [
    {
      hostname: import.meta.env.VITE_PROD_DOMAIN || 'mc.exbytestudios.com',
      fingerprint: 'PLACEHOLDER_REAL_CERTIFICATE_FINGERPRINT_HERE',
      algorithm: HASH_ALGORITHM.SHA256,
      lastVerified: new Date().toISOString(),
    },
    {
      hostname: import.meta.env.VITE_BACKUP_DOMAIN || 'backup.mc.exbytestudios.com',
      fingerprint: 'PLACEHOLDER_BACKUP_CERTIFICATE_FINGERPRINT_HERE',
      algorithm: HASH_ALGORITHM.SHA256,
      lastVerified: new Date().toISOString(),
    },
  ];

  private static readonly MAX_CERTIFICATE_AGE_DAYS = CERTIFICATE_CONSTANTS.MAX_AGE_DAYS;

  /**
   * Verify certificate for host
   */
  static async verifyCertificate(hostname: string): Promise<boolean> {
    try {
      if (typeof window !== 'undefined' && (window as any).electronAPI) {
        const certificate = await (window as any).electronAPI.getCertificate(hostname);
        return this.validateCertificate(hostname, certificate);
      }

      return this.validateCertificateBrowser(hostname);
    } catch (error) {
      console.error('Certificate verification failed:', error);
      return false;
    }
  }

  /**
   * Validate certificate against pinned certificates
   */
  private static validateCertificate(hostname: string, certificate: CertificateInfo): boolean {
    const pinnedCert = this.PINNED_CERTIFICATES.find((cert) => cert.hostname === hostname);

    if (!pinnedCert) {
      console.warn(`No pinned certificate found for hostname: ${hostname}`);
      return true;
    }

    if (certificate.fingerprint.toLowerCase() !== pinnedCert.fingerprint.toLowerCase()) {
      console.error(`Certificate fingerprint mismatch for ${hostname}:`, {
        expected: pinnedCert.fingerprint,
        actual: certificate.fingerprint,
      });
      return false;
    }

    if (certificate.algorithm !== pinnedCert.algorithm) {
      console.error(`Certificate algorithm mismatch for ${hostname}:`, {
        expected: pinnedCert.algorithm,
        actual: certificate.algorithm,
      });
      return false;
    }

    const validTo = new Date(certificate.validTo);
    const now = new Date();

    if (validTo < now) {
      console.error(`Certificate expired for ${hostname}:`, {
        validTo: certificate.validTo,
        now: now.toISOString(),
      });
      return false;
    }

    const validFrom = new Date(certificate.validFrom);
    const ageInDays = (now.getTime() - validFrom.getTime()) / MILLISECONDS_PER_DAY;

    if (ageInDays > this.MAX_CERTIFICATE_AGE_DAYS) {
      console.warn(`Certificate is too old for ${hostname}:`, {
        ageInDays,
        maxAge: this.MAX_CERTIFICATE_AGE_DAYS,
      });
    }

    console.log(`Certificate validation passed for ${hostname}`);
    return true;
  }

  /**
   * Validation for browser environment (limited)
   */
  private static validateCertificateBrowser(hostname: string): boolean {
    if (window.location.protocol !== CERTIFICATE_CONSTANTS.HTTPS_PROTOCOL) {
      console.error('Non-HTTPS connection detected');
      return false;
    }

    const expectedHostname = this.PINNED_CERTIFICATES.find((cert) => cert.hostname === hostname);
    if (!expectedHostname) {
      return true;
    }

    console.log(`HTTPS validation passed for ${hostname}`);
    return true;
  }

  /**
   * Get pinned certificate information for host
   */
  static getPinnedCertificate(hostname: string): PinnedCertificate | null {
    return this.PINNED_CERTIFICATES.find((cert) => cert.hostname === hostname) || null;
  }

  /**
   * Add new pinned certificate
   */
  static addPinnedCertificate(certificate: PinnedCertificate): void {
    const existingIndex = this.PINNED_CERTIFICATES.findIndex(
      (cert) => cert.hostname === certificate.hostname
    );

    if (existingIndex >= 0) {
      this.PINNED_CERTIFICATES[existingIndex] = certificate;
    } else {
      this.PINNED_CERTIFICATES.push(certificate);
    }

    console.log(`Added pinned certificate for ${certificate.hostname}`);
  }

  /**
   * Remove pinned certificate
   */
  static removePinnedCertificate(hostname: string): boolean {
    const index = this.PINNED_CERTIFICATES.findIndex((cert) => cert.hostname === hostname);

    if (index >= 0) {
      this.PINNED_CERTIFICATES.splice(index, 1);
      console.log(`Removed pinned certificate for ${hostname}`);
      return true;
    }

    return false;
  }

  /**
   * Get all pinned certificates
   */
  static getAllPinnedCertificates(): PinnedCertificate[] {
    return [...this.PINNED_CERTIFICATES];
  }

  /**
   * Check if pinned certificate exists for host
   */
  static hasPinnedCertificate(hostname: string): boolean {
    return this.PINNED_CERTIFICATES.some((cert) => cert.hostname === hostname);
  }

  /**
   * Update certificate fingerprint
   */
  static updateCertificateFingerprint(hostname: string, newFingerprint: string): boolean {
    const certificate = this.getPinnedCertificate(hostname);

    if (certificate) {
      certificate.fingerprint = newFingerprint;
      certificate.lastVerified = new Date().toISOString();
      console.log(`Updated certificate fingerprint for ${hostname}`);
      return true;
    }

    return false;
  }

  /**
   * Validate all pinned certificates
   */
  static async validateAllCertificates(): Promise<Record<string, boolean>> {
    const results: Record<string, boolean> = {};

    for (const certificate of this.PINNED_CERTIFICATES) {
      try {
        results[certificate.hostname] = await this.verifyCertificate(certificate.hostname);
      } catch (error) {
        console.error(`Failed to validate certificate for ${certificate.hostname}:`, error);
        results[certificate.hostname] = false;
      }
    }

    return results;
  }

  /**
   * Get pinned certificates statistics
   */
  static getStatistics(): {
    totalCertificates: number;
    certificatesByAlgorithm: Record<string, number>;
    oldestCertificate: PinnedCertificate | null;
    newestCertificate: PinnedCertificate | null;
  } {
    const certificates = this.PINNED_CERTIFICATES;

    const certificatesByAlgorithm = certificates.reduce(
      (acc, cert) => {
        acc[cert.algorithm] = (acc[cert.algorithm] || 0) + 1;
        return acc;
      },
      {} as Record<string, number>
    );

    const sortedByDate = [...certificates].sort(
      (a, b) => new Date(a.lastVerified).getTime() - new Date(b.lastVerified).getTime()
    );

    return {
      totalCertificates: certificates.length,
      certificatesByAlgorithm,
      oldestCertificate: sortedByDate[0] || null,
      newestCertificate: sortedByDate[sortedByDate.length - 1] || null,
    };
  }
}

const FINGERPRINT_PATTERNS = {
  SHA256: /^[a-f0-9]{64}$/,
  SHA1: /^[a-f0-9]{40}$/,
} as const;

/**
 * Certificate utilities
 */
export class CertificateUtils {
  /**
   * Compute SHA-256 fingerprint of certificate
   */
  static async computeSHA256Fingerprint(certificateData: ArrayBuffer): Promise<string> {
    const hashBuffer = await crypto.subtle.digest('SHA-256', certificateData);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray
      .map((b) => b.toString(HEX_FORMAT.RADIX).padStart(HEX_FORMAT.PAD_LENGTH, HEX_FORMAT.PAD_CHAR))
      .join(':')
      .toUpperCase();
  }

  /**
   * Compute SHA-1 fingerprint of certificate
   */
  static async computeSHA1Fingerprint(certificateData: ArrayBuffer): Promise<string> {
    const hashBuffer = await crypto.subtle.digest('SHA-1', certificateData);
    const hashArray = Array.from(new Uint8Array(hashBuffer));
    return hashArray
      .map((b) => b.toString(HEX_FORMAT.RADIX).padStart(HEX_FORMAT.PAD_LENGTH, HEX_FORMAT.PAD_CHAR))
      .join(':')
      .toUpperCase();
  }

  /**
   * Format fingerprint for display
   */
  static formatFingerprint(fingerprint: string): string {
    return fingerprint
      .toUpperCase()
      .replace(/(.{2})/g, '$1:')
      .slice(0, -1);
  }

  /**
   * Validate fingerprint format
   */
  static isValidFingerprint(fingerprint: string): boolean {
    const cleanFingerprint = fingerprint.replace(/:/g, '').toLowerCase();
    return (
      FINGERPRINT_PATTERNS.SHA1.test(cleanFingerprint) ||
      FINGERPRINT_PATTERNS.SHA256.test(cleanFingerprint)
    );
  }

  /**
   * Load current certificate pins from server
   */
  async loadDynamicPins(baseUrl: string): Promise<boolean> {
    try {
      const response = await fetch(
        `${baseUrl}${API_CONFIG.ENDPOINTS.SECURITY.CERTIFICATE_PINS}`,
        {
          method: 'GET',
          headers: {
            'Content-Type': 'application/json',
          },
        }
      );

      if (!response.ok) {
        throw new Error(`Failed to load certificate pins: ${response.status}`);
      }

      const config: CertificatePinConfig = await response.json();

      CertificatePinningService.pinCache = {
        config,
        lastUpdated: Date.now(),
        ttl: CertificatePinningService.CACHE_TTL,
      };

      this.updateStaticPins(config);

      console.log('Certificate pins loaded successfully', {
        version: config.version,
        hosts: Object.keys(config.pins),
        fallbackEnabled: config.fallback_enabled,
      });

      return true;
    } catch (error) {
      console.error('Failed to load dynamic certificate pins:', error);
      return false;
    }
  }

  /**
   * Update static pins from configuration
   */
  public updateStaticPins(config: CertificatePinConfig): void {
    const newPinnedCerts: PinnedCertificate[] = [];

    for (const [hostname, pins] of Object.entries(config.pins)) {
      for (const pin of pins) {
        newPinnedCerts.push({
          hostname,
          fingerprint: pin,
          algorithm: HASH_ALGORITHM.SHA256,
          lastVerified: new Date().toISOString(),
        });
      }
    }

    if (config.fallback_enabled) {
      for (const [hostname, pins] of Object.entries(config.backup_pins)) {
        for (const pin of pins) {
          newPinnedCerts.push({
            hostname,
            fingerprint: pin,
            algorithm: HASH_ALGORITHM.SHA256,
            lastVerified: new Date().toISOString(),
          });
        }
      }
    }

    (CertificatePinningService as any).PINNED_CERTIFICATES = newPinnedCerts;
  }

  /**
   * Check if pins cache is valid
   */
  isPinCacheValid(): boolean {
    if (!CertificatePinningService.pinCache) {
      return false;
    }

    const now = Date.now();
    return (
      now - CertificatePinningService.pinCache.lastUpdated <
      CertificatePinningService.pinCache.ttl
    );
  }

  /**
   * Get pins cache information
   */
  static getPinCacheInfo(): { cached: boolean; lastUpdated?: string; ttl?: number } {
    if (!CertificatePinningService.pinCache) {
      return { cached: false };
    }

    return {
      cached: true,
      lastUpdated: new Date(CertificatePinningService.pinCache.lastUpdated).toISOString(),
      ttl: CertificatePinningService.pinCache.ttl,
    };
  }

  /**
   * Clear pins cache
   */
  static clearPinCache(): void {
    CertificatePinningService.pinCache = null;
  }
}

export const certificatePinning = new CertificatePinningService();
export const certificateUtils = CertificateUtils;
