/**
 * SSL Manager for Onebox
 *
 * Manages SSL certificates via Let's Encrypt (using smartacme)
 */

import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import { OneboxDatabase } from './database.ts';
import { SqliteCertManager } from './certmanager.ts';

export class OneboxSslManager {
  private oneboxRef: any;
  private database: OneboxDatabase;
  private smartacme: plugins.smartacme.SmartAcme | null = null;
  private certManager: SqliteCertManager | null = null;
  private acmeEmail: string | null = null;

  constructor(oneboxRef: any) {
    this.oneboxRef = oneboxRef;
    this.database = oneboxRef.database;
  }

  /**
   * Initialize SSL manager
   */
  async init(): Promise<void> {
    try {
      // Get ACME email from settings
      const acmeEmail = this.database.getSetting('acmeEmail');

      if (!acmeEmail) {
        logger.warn('ACME email not configured. SSL certificate management will be limited.');
        logger.info('Configure with: onebox config set acmeEmail <email>');
        return;
      }

      this.acmeEmail = acmeEmail;

      // Get Cloudflare API key (reuse from DNS manager)
      const cfApiKey = await this.database.getSecretSetting('cloudflareToken');

      if (!cfApiKey) {
        logger.warn('Cloudflare API key not configured. SSL certificate management will be limited.');
        logger.info('Configure with: onebox config set cloudflareToken <key>');
        return;
      }

      // Get ACME environment (default: production)
      const acmeEnvironment = this.database.getSetting('acmeEnvironment') || 'production';

      if (!['production', 'integration'].includes(acmeEnvironment)) {
        throw new Error('acmeEnvironment must be "production" or "integration"');
      }

      // Initialize certificate manager
      this.certManager = new SqliteCertManager(this.database);
      await this.certManager.init();

      // Initialize Cloudflare DNS provider for DNS-01 challenge
      const cfAccount = new plugins.cloudflare.CloudflareAccount(cfApiKey);

      // Create DNS-01 challenge handler
      const dns01Handler = new plugins.smartacme.handlers.Dns01Handler(cfAccount);

      // Initialize SmartACME with proper configuration
      this.smartacme = new plugins.smartacme.SmartAcme({
        accountEmail: acmeEmail,
        certManager: this.certManager,
        environment: acmeEnvironment as 'production' | 'integration',
        challengeHandlers: [dns01Handler],
        challengePriority: ['dns-01'], // Prefer DNS-01 challenges
      });

      // Start SmartACME
      await this.smartacme.start();

      logger.success('SSL manager initialized with SmartACME DNS-01 challenge');
    } catch (error) {
      logger.error(`Failed to initialize SSL manager: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Check if SSL manager is configured
   */
  isConfigured(): boolean {
    return this.smartacme !== null && this.acmeEmail !== null;
  }

  /**
   * Acquire certificate and return certificate data (for CertRequirementManager)
   * Returns certificate PEM content and expiry information
   */
  async acquireCertificate(
    domain: string,
    includeWildcard = false
  ): Promise<{
    certPem: string;
    keyPem: string;
    fullchainPem: string;
    expiryDate: number;
    issuer: string;
  }> {
    try {
      if (!this.isConfigured()) {
        throw new Error('SSL manager not configured');
      }

      logger.info(`Acquiring SSL certificate for ${domain} via SmartACME DNS-01...`);

      // Use SmartACME to obtain certificate via DNS-01 challenge
      const cert = await this.smartacme!.getCertificateForDomain(domain, {
        includeWildcard,
      });

      logger.success(`SSL certificate obtained for ${domain}`);
      logger.info(`Certificate valid until: ${new Date(cert.validUntil).toISOString()}`);

      // Reload certificates in reverse proxy
      await this.oneboxRef.reverseProxy.reloadCertificates();

      // The certManager stores the cert to database during getCertificateForDomain
      // Look up the PEM content from the database
      const dbCert = this.database.getSSLCertificate(domain);
      if (!dbCert) {
        throw new Error(`Certificate stored but not found in database for ${domain}`);
      }

      // Return certificate data from database
      return {
        certPem: dbCert.certPem,
        keyPem: dbCert.keyPem,
        fullchainPem: dbCert.fullchainPem,
        expiryDate: cert.validUntil,
        issuer: dbCert.issuer || "Let's Encrypt",
      };
    } catch (error) {
      logger.error(`Failed to acquire certificate for ${domain}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Obtain SSL certificate for a domain using SmartACME
   */
  async obtainCertificate(domain: string, includeWildcard = false): Promise<void> {
    try {
      if (!this.isConfigured()) {
        throw new Error('SSL manager not configured');
      }

      logger.info(`Obtaining SSL certificate for ${domain} via SmartACME DNS-01...`);

      // Check if certificate already exists and is valid
      const existingCert = await this.certManager!.retrieveCertificate(domain);
      if (existingCert && existingCert.isStillValid()) {
        logger.info(`Valid certificate already exists for ${domain}`);
        return;
      }

      // Use SmartACME to obtain certificate via DNS-01 challenge
      const cert = await this.smartacme!.getCertificateForDomain(domain, {
        includeWildcard,
      });

      logger.success(`SSL certificate obtained for ${domain}`);
      logger.info(`Certificate valid until: ${new Date(cert.validUntil).toISOString()}`);

      // Reload certificates in reverse proxy
      await this.oneboxRef.reverseProxy.reloadCertificates();
    } catch (error) {
      logger.error(`Failed to obtain certificate for ${domain}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Obtain certificate using certbot
   */
  private async obtainCertificateWithCertbot(domain: string): Promise<void> {
    try {
      logger.info(`Running certbot for ${domain}...`);

      // Use webroot method (nginx serves .well-known/acme-challenge)
      const command = new Deno.Command('certbot', {
        args: [
          'certonly',
          '--webroot',
          '--webroot-path=/var/www/certbot',
          '--email',
          this.acmeEmail!,
          '--agree-tos',
          '--no-eff-email',
          '--domain',
          domain,
          '--non-interactive',
        ],
        stdout: 'piped',
        stderr: 'piped',
      });

      const { code, stderr } = await command.output();

      if (code !== 0) {
        const errorMsg = new TextDecoder().decode(stderr);
        throw new Error(`Certbot failed: ${errorMsg}`);
      }

      logger.success(`Certbot obtained certificate for ${domain}`);
    } catch (error) {
      throw new Error(`Failed to run certbot: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Renew certificate for a domain using SmartACME
   */
  async renewCertificate(domain: string): Promise<void> {
    try {
      if (!this.isConfigured()) {
        throw new Error('SSL manager not configured');
      }

      logger.info(`Renewing SSL certificate for ${domain} via SmartACME...`);

      // SmartACME will check if renewal is needed and obtain a new certificate
      const cert = await this.smartacme!.getCertificateForDomain(domain);

      logger.success(`Certificate renewed for ${domain}`);
      logger.info(`Certificate valid until: ${new Date(cert.validUntil).toISOString()}`);

      // Reload certificates in reverse proxy
      await this.oneboxRef.reverseProxy.reloadCertificates();
    } catch (error) {
      logger.error(`Failed to renew certificate for ${domain}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * List all certificates
   */
  listCertificates() {
    return this.database.getAllSSLCertificates();
  }

  /**
   * Get certificate info for a domain
   */
  getCertificate(domain: string) {
    return this.database.getSSLCertificate(domain);
  }

  /**
   * Check certificates that are expiring soon and renew them
   */
  async renewExpiring(): Promise<void> {
    try {
      if (!this.isConfigured()) {
        logger.warn('SSL manager not configured, skipping renewal check');
        return;
      }

      logger.info('Checking for expiring certificates...');

      const certificates = this.listCertificates();

      for (const dbCert of certificates) {
        try {
          // Retrieve certificate from cert manager to check if it should be renewed
          const cert = await this.certManager!.retrieveCertificate(dbCert.domain);

          if (cert && cert.shouldBeRenewed()) {
            logger.info(`Certificate for ${dbCert.domain} needs renewal, renewing...`);
            await this.renewCertificate(dbCert.domain);
          }
        } catch (error) {
          logger.error(`Failed to renew ${dbCert.domain}: ${getErrorMessage(error)}`);
          // Continue with other certificates
        }
      }

      logger.success('Certificate renewal check complete');
    } catch (error) {
      logger.error(`Failed to check expiring certificates: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Force renewal of all certificates
   */
  async renewAll(): Promise<void> {
    try {
      logger.info('Renewing all certificates...');

      const command = new Deno.Command('certbot', {
        args: ['renew', '--force-renewal', '--non-interactive'],
        stdout: 'piped',
        stderr: 'piped',
      });

      const { code, stderr } = await command.output();

      if (code !== 0) {
        const errorMsg = new TextDecoder().decode(stderr);
        throw new Error(`Certbot renewal failed: ${errorMsg}`);
      }

      logger.success('All certificates renewed');

      // Reload certificates in reverse proxy
      await this.oneboxRef.reverseProxy.reloadCertificates();
    } catch (error) {
      logger.error(`Failed to renew all certificates: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Check if certbot is installed
   */
  async isCertbotInstalled(): Promise<boolean> {
    try {
      const command = new Deno.Command('which', {
        args: ['certbot'],
        stdout: 'piped',
        stderr: 'piped',
      });

      const { code } = await command.output();
      return code === 0;
    } catch {
      return false;
    }
  }

  /**
   * Get certificate expiry date from file
   */
  async getCertificateExpiry(domain: string): Promise<Date | null> {
    try {
      const certPath = `/etc/letsencrypt/live/${domain}/cert.pem`;

      const command = new Deno.Command('openssl', {
        args: ['x509', '-enddate', '-noout', '-in', certPath],
        stdout: 'piped',
        stderr: 'piped',
      });

      const { code, stdout } = await command.output();

      if (code !== 0) {
        return null;
      }

      const output = new TextDecoder().decode(stdout);
      const match = output.match(/notAfter=(.+)/);

      if (match) {
        return new Date(match[1]);
      }

      return null;
    } catch (error) {
      logger.error(`Failed to get certificate expiry for ${domain}: ${getErrorMessage(error)}`);
      return null;
    }
  }
}
