import * as plugins from '../plugins.js';
import { IncomingMessage, ServerResponse } from 'http';
import * as fs from 'fs';
import * as path from 'path';

/**
 * Custom error classes for better error handling
 */
export class Port80HandlerError extends Error {
  constructor(message: string) {
    super(message);
    this.name = 'Port80HandlerError';
  }
}

export class CertificateError extends Port80HandlerError {
  constructor(
    message: string,
    public readonly domain: string,
    public readonly isRenewal: boolean = false
  ) {
    super(`${message} for domain ${domain}${isRenewal ? ' (renewal)' : ''}`);
    this.name = 'CertificateError';
  }
}

export class ServerError extends Port80HandlerError {
  constructor(message: string, public readonly code?: string) {
    super(message);
    this.name = 'ServerError';
  }
}

/**
 * Domain forwarding configuration
 */
export interface IForwardConfig {
  ip: string;
  port: number;
}

/**
 * Domain configuration options
 */
export interface IDomainOptions {
  domainName: string;
  sslRedirect: boolean;   // if true redirects the request to port 443
  acmeMaintenance: boolean; // tries to always have a valid cert for this domain
  forward?: IForwardConfig; // forwards all http requests to that target
  acmeForward?: IForwardConfig; // forwards letsencrypt requests to this config
}

/**
 * Represents a domain configuration with certificate status information
 */
interface IDomainCertificate {
  options: IDomainOptions;
  certObtained: boolean;
  obtainingInProgress: boolean;
  certificate?: string;
  privateKey?: string;
  challengeToken?: string;
  challengeKeyAuthorization?: string;
  expiryDate?: Date;
  lastRenewalAttempt?: Date;
}

/**
 * Configuration options for the Port80Handler
 */
interface IPort80HandlerOptions {
  port?: number;
  contactEmail?: string;
  useProduction?: boolean;
  renewThresholdDays?: number;
  httpsRedirectPort?: number;
  renewCheckIntervalHours?: number;
  enabled?: boolean; // Whether ACME is enabled at all
  autoRenew?: boolean; // Whether to automatically renew certificates
  certificateStore?: string; // Directory to store certificates
  skipConfiguredCerts?: boolean; // Skip domains that already have certificates
}

/**
 * Certificate data that can be emitted via events or set from outside
 */
export interface ICertificateData {
  domain: string;
  certificate: string;
  privateKey: string;
  expiryDate: Date;
}

/**
 * Events emitted by the Port80Handler
 */
export enum Port80HandlerEvents {
  CERTIFICATE_ISSUED = 'certificate-issued',
  CERTIFICATE_RENEWED = 'certificate-renewed',
  CERTIFICATE_FAILED = 'certificate-failed',
  CERTIFICATE_EXPIRING = 'certificate-expiring',
  MANAGER_STARTED = 'manager-started',
  MANAGER_STOPPED = 'manager-stopped',
  REQUEST_FORWARDED = 'request-forwarded',
}

/**
 * Certificate failure payload type
 */
export interface ICertificateFailure {
  domain: string;
  error: string;
  isRenewal: boolean;
}

/**
 * Certificate expiry payload type
 */
export interface ICertificateExpiring {
  domain: string;
  expiryDate: Date;
  daysRemaining: number;
}

/**
 * Port80Handler with ACME certificate management and request forwarding capabilities
 * Now with glob pattern support for domain matching
 */
export class Port80Handler extends plugins.EventEmitter {
  private domainCertificates: Map<string, IDomainCertificate>;
  private server: plugins.http.Server | null = null;
  private acmeClient: plugins.acme.Client | null = null;
  private accountKey: string | null = null;
  private renewalTimer: NodeJS.Timeout | null = null;
  private isShuttingDown: boolean = false;
  private options: Required<IPort80HandlerOptions>;

  /**
   * Creates a new Port80Handler
   * @param options Configuration options
   */
  constructor(options: IPort80HandlerOptions = {}) {
    super();
    this.domainCertificates = new Map<string, IDomainCertificate>();
    
    // Default options
    this.options = {
      port: options.port ?? 80,
      contactEmail: options.contactEmail ?? 'admin@example.com',
      useProduction: options.useProduction ?? false, // Safer default: staging
      renewThresholdDays: options.renewThresholdDays ?? 10, // Changed to 10 days as per requirements
      httpsRedirectPort: options.httpsRedirectPort ?? 443,
      renewCheckIntervalHours: options.renewCheckIntervalHours ?? 24,
      enabled: options.enabled ?? true, // Enable by default
      autoRenew: options.autoRenew ?? true, // Auto-renew by default
      certificateStore: options.certificateStore ?? './certs', // Default store location
      skipConfiguredCerts: options.skipConfiguredCerts ?? false
    };
  }

  /**
   * Starts the HTTP server for ACME challenges
   */
  public async start(): Promise<void> {
    if (this.server) {
      throw new ServerError('Server is already running');
    }
    
    if (this.isShuttingDown) {
      throw new ServerError('Server is shutting down');
    }

    // Skip if disabled
    if (this.options.enabled === false) {
      console.log('Port80Handler is disabled, skipping start');
      return;
    }

    return new Promise((resolve, reject) => {
      try {
        // Load certificates from store if enabled
        if (this.options.certificateStore) {
          this.loadCertificatesFromStore();
        }
        
        this.server = plugins.http.createServer((req, res) => this.handleRequest(req, res));
        
        this.server.on('error', (error: NodeJS.ErrnoException) => {
          if (error.code === 'EACCES') {
            reject(new ServerError(`Permission denied to bind to port ${this.options.port}. Try running with elevated privileges or use a port > 1024.`, error.code));
          } else if (error.code === 'EADDRINUSE') {
            reject(new ServerError(`Port ${this.options.port} is already in use.`, error.code));
          } else {
            reject(new ServerError(error.message, error.code));
          }
        });
        
        this.server.listen(this.options.port, () => {
          console.log(`Port80Handler is listening on port ${this.options.port}`);
          this.startRenewalTimer();
          this.emit(Port80HandlerEvents.MANAGER_STARTED, this.options.port);
          
          // Start certificate process for domains with acmeMaintenance enabled
          for (const [domain, domainInfo] of this.domainCertificates.entries()) {
            // Skip glob patterns for certificate issuance
            if (this.isGlobPattern(domain)) {
              console.log(`Skipping initial certificate for glob pattern: ${domain}`);
              continue;
            }
            
            if (domainInfo.options.acmeMaintenance && !domainInfo.certObtained && !domainInfo.obtainingInProgress) {
              this.obtainCertificate(domain).catch(err => {
                console.error(`Error obtaining initial certificate for ${domain}:`, err);
              });
            }
          }
          
          resolve();
        });
      } catch (error) {
        const message = error instanceof Error ? error.message : 'Unknown error starting server';
        reject(new ServerError(message));
      }
    });
  }

  /**
   * Stops the HTTP server and renewal timer
   */
  public async stop(): Promise<void> {
    if (!this.server) {
      return;
    }
    
    this.isShuttingDown = true;
    
    // Stop the renewal timer
    if (this.renewalTimer) {
      clearInterval(this.renewalTimer);
      this.renewalTimer = null;
    }

    return new Promise<void>((resolve) => {
      if (this.server) {
        this.server.close(() => {
          this.server = null;
          this.isShuttingDown = false;
          this.emit(Port80HandlerEvents.MANAGER_STOPPED);
          resolve();
        });
      } else {
        this.isShuttingDown = false;
        resolve();
      }
    });
  }

  /**
   * Adds a domain with configuration options
   * @param options Domain configuration options
   */
  public addDomain(options: IDomainOptions): void {
    if (!options.domainName || typeof options.domainName !== 'string') {
      throw new Port80HandlerError('Invalid domain name');
    }
    
    const domainName = options.domainName;
    
    if (!this.domainCertificates.has(domainName)) {
      this.domainCertificates.set(domainName, {
        options,
        certObtained: false,
        obtainingInProgress: false
      });
      
      console.log(`Domain added: ${domainName} with configuration:`, {
        sslRedirect: options.sslRedirect,
        acmeMaintenance: options.acmeMaintenance,
        hasForward: !!options.forward,
        hasAcmeForward: !!options.acmeForward
      });
      
      // If acmeMaintenance is enabled and not a glob pattern, start certificate process immediately
      if (options.acmeMaintenance && this.server && !this.isGlobPattern(domainName)) {
        this.obtainCertificate(domainName).catch(err => {
          console.error(`Error obtaining initial certificate for ${domainName}:`, err);
        });
      }
    } else {
      // Update existing domain with new options
      const existing = this.domainCertificates.get(domainName)!;
      existing.options = options;
      console.log(`Domain ${domainName} configuration updated`);
    }
  }

  /**
   * Removes a domain from management
   * @param domain The domain to remove
   */
  public removeDomain(domain: string): void {
    if (this.domainCertificates.delete(domain)) {
      console.log(`Domain removed: ${domain}`);
    }
  }
  
  /**
   * Sets a certificate for a domain directly (for externally obtained certificates)
   * @param domain The domain for the certificate
   * @param certificate The certificate (PEM format)
   * @param privateKey The private key (PEM format)
   * @param expiryDate Optional expiry date
   */
  public setCertificate(domain: string, certificate: string, privateKey: string, expiryDate?: Date): void {
    if (!domain || !certificate || !privateKey) {
      throw new Port80HandlerError('Domain, certificate and privateKey are required');
    }
    
    // Don't allow setting certificates for glob patterns
    if (this.isGlobPattern(domain)) {
      throw new Port80HandlerError('Cannot set certificate for glob pattern domains');
    }
    
    let domainInfo = this.domainCertificates.get(domain);
    
    if (!domainInfo) {
      // Create default domain options if not already configured
      const defaultOptions: IDomainOptions = {
        domainName: domain,
        sslRedirect: true,
        acmeMaintenance: true
      };
      
      domainInfo = { 
        options: defaultOptions, 
        certObtained: false, 
        obtainingInProgress: false 
      };
      this.domainCertificates.set(domain, domainInfo);
    }
    
    domainInfo.certificate = certificate;
    domainInfo.privateKey = privateKey;
    domainInfo.certObtained = true;
    domainInfo.obtainingInProgress = false;
    
    if (expiryDate) {
      domainInfo.expiryDate = expiryDate;
    } else {
      // Extract expiry date from certificate
      domainInfo.expiryDate = this.extractExpiryDateFromCertificate(certificate, domain);
    }
    
    console.log(`Certificate set for ${domain}`);
    
    // Save certificate to store if enabled
    if (this.options.certificateStore) {
      this.saveCertificateToStore(domain, certificate, privateKey);
    }
    
    // Emit certificate event
    this.emitCertificateEvent(Port80HandlerEvents.CERTIFICATE_ISSUED, {
      domain,
      certificate,
      privateKey,
      expiryDate: domainInfo.expiryDate || this.getDefaultExpiryDate()
    });
  }
  
  /**
   * Gets the certificate for a domain if it exists
   * @param domain The domain to get the certificate for
   */
  public getCertificate(domain: string): ICertificateData | null {
    // Can't get certificates for glob patterns
    if (this.isGlobPattern(domain)) {
      return null;
    }
    
    const domainInfo = this.domainCertificates.get(domain);
    
    if (!domainInfo || !domainInfo.certObtained || !domainInfo.certificate || !domainInfo.privateKey) {
      return null;
    }
    
    return {
      domain,
      certificate: domainInfo.certificate,
      privateKey: domainInfo.privateKey,
      expiryDate: domainInfo.expiryDate || this.getDefaultExpiryDate()
    };
  }

  /**
   * Saves a certificate to the filesystem store
   * @param domain The domain for the certificate
   * @param certificate The certificate (PEM format)
   * @param privateKey The private key (PEM format)
   * @private
   */
  private saveCertificateToStore(domain: string, certificate: string, privateKey: string): void {
    // Skip if certificate store is not enabled
    if (!this.options.certificateStore) return;
    
    try {
      const storePath = this.options.certificateStore;
      
      // Ensure the directory exists
      if (!fs.existsSync(storePath)) {
        fs.mkdirSync(storePath, { recursive: true });
        console.log(`Created certificate store directory: ${storePath}`);
      }
      
      const certPath = path.join(storePath, `${domain}.cert.pem`);
      const keyPath = path.join(storePath, `${domain}.key.pem`);
      
      // Write certificate and private key files
      fs.writeFileSync(certPath, certificate);
      fs.writeFileSync(keyPath, privateKey);
      
      // Set secure permissions for private key
      try {
        fs.chmodSync(keyPath, 0o600);
      } catch (err) {
        console.log(`Warning: Could not set secure permissions on ${keyPath}`);
      }
      
      console.log(`Saved certificate for ${domain} to ${certPath}`);
    } catch (err) {
      console.error(`Error saving certificate for ${domain}:`, err);
    }
  }

  /**
   * Loads certificates from the certificate store
   * @private
   */
  private loadCertificatesFromStore(): void {
    if (!this.options.certificateStore) return;
    
    try {
      const storePath = this.options.certificateStore;
      
      // Ensure the directory exists
      if (!fs.existsSync(storePath)) {
        fs.mkdirSync(storePath, { recursive: true });
        console.log(`Created certificate store directory: ${storePath}`);
        return;
      }
      
      // Get list of certificate files
      const files = fs.readdirSync(storePath);
      const certFiles = files.filter(file => file.endsWith('.cert.pem'));
      
      // Load each certificate
      for (const certFile of certFiles) {
        const domain = certFile.replace('.cert.pem', '');
        const keyFile = `${domain}.key.pem`;
        
        // Skip if key file doesn't exist
        if (!files.includes(keyFile)) {
          console.log(`Warning: Found certificate for ${domain} but no key file`);
          continue;
        }
        
        // Skip if we should skip configured certs
        if (this.options.skipConfiguredCerts) {
          const domainInfo = this.domainCertificates.get(domain);
          if (domainInfo && domainInfo.certObtained) {
            console.log(`Skipping already configured certificate for ${domain}`);
            continue;
          }
        }
        
        // Load certificate and key
        try {
          const certificate = fs.readFileSync(path.join(storePath, certFile), 'utf8');
          const privateKey = fs.readFileSync(path.join(storePath, keyFile), 'utf8');
          
          // Extract expiry date
          let expiryDate: Date | undefined;
          try {
            const matches = certificate.match(/Not After\s*:\s*(.*?)(?:\n|$)/i);
            if (matches && matches[1]) {
              expiryDate = new Date(matches[1]);
            }
          } catch (err) {
            console.log(`Warning: Could not extract expiry date from certificate for ${domain}`);
          }
          
          // Check if domain is already registered
          let domainInfo = this.domainCertificates.get(domain);
          if (!domainInfo) {
            // Register domain if not already registered
            domainInfo = {
              options: {
                domainName: domain,
                sslRedirect: true,
                acmeMaintenance: true
              },
              certObtained: false,
              obtainingInProgress: false
            };
            this.domainCertificates.set(domain, domainInfo);
          }
          
          // Set certificate
          domainInfo.certificate = certificate;
          domainInfo.privateKey = privateKey;
          domainInfo.certObtained = true;
          domainInfo.expiryDate = expiryDate;
          
          console.log(`Loaded certificate for ${domain} from store, valid until ${expiryDate?.toISOString() || 'unknown'}`);
        } catch (err) {
          console.error(`Error loading certificate for ${domain}:`, err);
        }
      }
    } catch (err) {
      console.error('Error loading certificates from store:', err);
    }
  }

  /**
   * Check if a domain is a glob pattern
   * @param domain Domain to check
   * @returns True if the domain is a glob pattern
   */
  private isGlobPattern(domain: string): boolean {
    return domain.includes('*');
  }
  
  /**
   * Get domain info for a specific domain, using glob pattern matching if needed
   * @param requestDomain The actual domain from the request
   * @returns The domain info or null if not found
   */
  private getDomainInfoForRequest(requestDomain: string): { domainInfo: IDomainCertificate, pattern: string } | null {
    // Try direct match first
    if (this.domainCertificates.has(requestDomain)) {
      return {
        domainInfo: this.domainCertificates.get(requestDomain)!,
        pattern: requestDomain
      };
    }
    
    // Then try glob patterns
    for (const [pattern, domainInfo] of this.domainCertificates.entries()) {
      if (this.isGlobPattern(pattern) && this.domainMatchesPattern(requestDomain, pattern)) {
        return { domainInfo, pattern };
      }
    }
    
    return null;
  }
  
  /**
   * Check if a domain matches a glob pattern
   * @param domain The domain to check
   * @param pattern The pattern to match against
   * @returns True if the domain matches the pattern
   */
  private domainMatchesPattern(domain: string, pattern: string): boolean {
    // Handle different glob pattern styles
    if (pattern.startsWith('*.')) {
      // *.example.com matches any subdomain
      const suffix = pattern.substring(2);
      return domain.endsWith(suffix) && domain.includes('.') && domain !== suffix;
    } else if (pattern.endsWith('.*')) {
      // example.* matches any TLD
      const prefix = pattern.substring(0, pattern.length - 2);
      const domainParts = domain.split('.');
      return domain.startsWith(prefix + '.') && domainParts.length >= 2;
    } else if (pattern === '*') {
      // Wildcard matches everything
      return true;
    } else {
      // Exact match (shouldn't reach here as we check exact matches first)
      return domain === pattern;
    }
  }

  /**
   * Lazy initialization of the ACME client
   * @returns An ACME client instance
   */
  private async getAcmeClient(): Promise<plugins.acme.Client> {
    if (this.acmeClient) {
      return this.acmeClient;
    }
    
    try {
      // Generate a new account key
      this.accountKey = (await plugins.acme.forge.createPrivateKey()).toString();
      
      this.acmeClient = new plugins.acme.Client({
        directoryUrl: this.options.useProduction 
          ? plugins.acme.directory.letsencrypt.production 
          : plugins.acme.directory.letsencrypt.staging,
        accountKey: this.accountKey,
      });
      
      // Create a new account
      await this.acmeClient.createAccount({
        termsOfServiceAgreed: true,
        contact: [`mailto:${this.options.contactEmail}`],
      });
      
      return this.acmeClient;
    } catch (error) {
      const message = error instanceof Error ? error.message : 'Unknown error initializing ACME client';
      throw new Port80HandlerError(`Failed to initialize ACME client: ${message}`);
    }
  }

  /**
   * Handles incoming HTTP requests
   * @param req The HTTP request
   * @param res The HTTP response
   */
  private handleRequest(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse): void {
    const hostHeader = req.headers.host;
    if (!hostHeader) {
      res.statusCode = 400;
      res.end('Bad Request: Host header is missing');
      return;
    }
    
    // Extract domain (ignoring any port in the Host header)
    const domain = hostHeader.split(':')[0];

    // Get domain config, using glob pattern matching if needed
    const domainMatch = this.getDomainInfoForRequest(domain);
    
    if (!domainMatch) {
      res.statusCode = 404;
      res.end('Domain not configured');
      return;
    }

    const { domainInfo, pattern } = domainMatch;
    const options = domainInfo.options;

    // If the request is for an ACME HTTP-01 challenge, handle it
    if (req.url && req.url.startsWith('/.well-known/acme-challenge/') && (options.acmeMaintenance || options.acmeForward)) {
      // Check if we should forward ACME requests
      if (options.acmeForward) {
        this.forwardRequest(req, res, options.acmeForward, 'ACME challenge');
        return;
      }
      
      // Only handle ACME challenges for non-glob patterns
      if (!this.isGlobPattern(pattern)) {
        this.handleAcmeChallenge(req, res, domain);
        return;
      }
    }

    // Check if we should forward non-ACME requests
    if (options.forward) {
      this.forwardRequest(req, res, options.forward, 'HTTP');
      return;
    }

    // If certificate exists and sslRedirect is enabled, redirect to HTTPS
    // (Skip for glob patterns as they won't have certificates)
    if (!this.isGlobPattern(pattern) && domainInfo.certObtained && options.sslRedirect) {
      const httpsPort = this.options.httpsRedirectPort;
      const portSuffix = httpsPort === 443 ? '' : `:${httpsPort}`;
      const redirectUrl = `https://${domain}${portSuffix}${req.url || '/'}`;
      
      res.statusCode = 301;
      res.setHeader('Location', redirectUrl);
      res.end(`Redirecting to ${redirectUrl}`);
      return;
    }
    
    // Handle case where certificate maintenance is enabled but not yet obtained
    // (Skip for glob patterns as they can't have certificates)
    if (!this.isGlobPattern(pattern) && options.acmeMaintenance && !domainInfo.certObtained) {
      // Trigger certificate issuance if not already running
      if (!domainInfo.obtainingInProgress) {
        this.obtainCertificate(domain).catch(err => {
          const errorMessage = err instanceof Error ? err.message : 'Unknown error';
          this.emit(Port80HandlerEvents.CERTIFICATE_FAILED, {
            domain,
            error: errorMessage,
            isRenewal: false
          });
          console.error(`Error obtaining certificate for ${domain}:`, err);
        });
      }
      
      res.statusCode = 503;
      res.end('Certificate issuance in progress, please try again later.');
      return;
    }
    
    // Default response for unhandled request
    res.statusCode = 404;
    res.end('No handlers configured for this request');
  }
  
  /**
   * Forwards an HTTP request to the specified target
   * @param req The original request
   * @param res The response object
   * @param target The forwarding target (IP and port)
   * @param requestType Type of request for logging
   */
  private forwardRequest(
    req: plugins.http.IncomingMessage, 
    res: plugins.http.ServerResponse,
    target: IForwardConfig,
    requestType: string
  ): void {
    const options = {
      hostname: target.ip,
      port: target.port,
      path: req.url,
      method: req.method,
      headers: { ...req.headers }
    };
    
    const domain = req.headers.host?.split(':')[0] || 'unknown';
    console.log(`Forwarding ${requestType} request for ${domain} to ${target.ip}:${target.port}`);
    
    const proxyReq = plugins.http.request(options, (proxyRes) => {
      // Copy status code
      res.statusCode = proxyRes.statusCode || 500;
      
      // Copy headers
      for (const [key, value] of Object.entries(proxyRes.headers)) {
        if (value) res.setHeader(key, value);
      }
      
      // Pipe response data
      proxyRes.pipe(res);
      
      this.emit(Port80HandlerEvents.REQUEST_FORWARDED, {
        domain,
        requestType,
        target: `${target.ip}:${target.port}`,
        statusCode: proxyRes.statusCode
      });
    });
    
    proxyReq.on('error', (error) => {
      console.error(`Error forwarding request to ${target.ip}:${target.port}:`, error);
      if (!res.headersSent) {
        res.statusCode = 502;
        res.end(`Proxy error: ${error.message}`);
      } else {
        res.end();
      }
    });
    
    // Pipe original request to proxy request
    if (req.readable) {
      req.pipe(proxyReq);
    } else {
      proxyReq.end();
    }
  }

  /**
   * Serves the ACME HTTP-01 challenge response
   * @param req The HTTP request
   * @param res The HTTP response
   * @param domain The domain for the challenge
   */
  private handleAcmeChallenge(req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse, domain: string): void {
    const domainInfo = this.domainCertificates.get(domain);
    if (!domainInfo) {
      res.statusCode = 404;
      res.end('Domain not configured');
      return;
    }
    
    // The token is the last part of the URL
    const urlParts = req.url?.split('/');
    const token = urlParts ? urlParts[urlParts.length - 1] : '';
    
    if (domainInfo.challengeToken === token && domainInfo.challengeKeyAuthorization) {
      res.statusCode = 200;
      res.setHeader('Content-Type', 'text/plain');
      res.end(domainInfo.challengeKeyAuthorization);
      console.log(`Served ACME challenge response for ${domain}`);
    } else {
      res.statusCode = 404;
      res.end('Challenge token not found');
    }
  }

  /**
   * Obtains a certificate for a domain using ACME HTTP-01 challenge
   * @param domain The domain to obtain a certificate for
   * @param isRenewal Whether this is a renewal attempt
   */
  private async obtainCertificate(domain: string, isRenewal: boolean = false): Promise<void> {
    // Don't allow certificate issuance for glob patterns
    if (this.isGlobPattern(domain)) {
      throw new CertificateError('Cannot obtain certificates for glob pattern domains', domain, isRenewal);
    }
    
    // Get the domain info
    const domainInfo = this.domainCertificates.get(domain);
    if (!domainInfo) {
      throw new CertificateError('Domain not found', domain, isRenewal);
    }
    
    // Verify that acmeMaintenance is enabled
    if (!domainInfo.options.acmeMaintenance) {
      console.log(`Skipping certificate issuance for ${domain} - acmeMaintenance is disabled`);
      return;
    }
    
    // Prevent concurrent certificate issuance
    if (domainInfo.obtainingInProgress) {
      console.log(`Certificate issuance already in progress for ${domain}`);
      return;
    }
    
    domainInfo.obtainingInProgress = true;
    domainInfo.lastRenewalAttempt = new Date();
    
    try {
      const client = await this.getAcmeClient();

      // Create a new order for the domain
      const order = await client.createOrder({
        identifiers: [{ type: 'dns', value: domain }],
      });

      // Get the authorizations for the order
      const authorizations = await client.getAuthorizations(order);
      
      // Process each authorization
      await this.processAuthorizations(client, domain, authorizations);

      // Generate a CSR and private key
      const [csrBuffer, privateKeyBuffer] = await plugins.acme.forge.createCsr({
        commonName: domain,
      });
      
      const csr = csrBuffer.toString();
      const privateKey = privateKeyBuffer.toString();

      // Finalize the order with our CSR
      await client.finalizeOrder(order, csr);
      
      // Get the certificate with the full chain
      const certificate = await client.getCertificate(order);

      // Store the certificate and key
      domainInfo.certificate = certificate;
      domainInfo.privateKey = privateKey;
      domainInfo.certObtained = true;
      
      // Clear challenge data
      delete domainInfo.challengeToken;
      delete domainInfo.challengeKeyAuthorization;
      
      // Extract expiry date from certificate
      domainInfo.expiryDate = this.extractExpiryDateFromCertificate(certificate, domain);

      console.log(`Certificate ${isRenewal ? 'renewed' : 'obtained'} for ${domain}`);
      
      // Save the certificate to the store if enabled
      if (this.options.certificateStore) {
        this.saveCertificateToStore(domain, certificate, privateKey);
      }
      
      // Emit the appropriate event
      const eventType = isRenewal 
        ? Port80HandlerEvents.CERTIFICATE_RENEWED 
        : Port80HandlerEvents.CERTIFICATE_ISSUED;
      
      this.emitCertificateEvent(eventType, {
        domain,
        certificate,
        privateKey,
        expiryDate: domainInfo.expiryDate || this.getDefaultExpiryDate()
      });
      
    } catch (error: any) {
      // Check for rate limit errors
      if (error.message && (
        error.message.includes('rateLimited') || 
        error.message.includes('too many certificates') || 
        error.message.includes('rate limit')
      )) {
        console.error(`Rate limit reached for ${domain}. Waiting before retry.`);
      } else {
        console.error(`Error during certificate issuance for ${domain}:`, error);
      }
      
      // Emit failure event
      this.emit(Port80HandlerEvents.CERTIFICATE_FAILED, {
        domain,
        error: error.message || 'Unknown error',
        isRenewal
      } as ICertificateFailure);
      
      throw new CertificateError(
        error.message || 'Certificate issuance failed',
        domain,
        isRenewal
      );
    } finally {
      // Reset flag whether successful or not
      domainInfo.obtainingInProgress = false;
    }
  }

  /**
   * Process ACME authorizations by verifying and completing challenges
   * @param client ACME client 
   * @param domain Domain name
   * @param authorizations Authorizations to process
   */
  private async processAuthorizations(
    client: plugins.acme.Client,
    domain: string,
    authorizations: plugins.acme.Authorization[]
  ): Promise<void> {
    const domainInfo = this.domainCertificates.get(domain);
    if (!domainInfo) {
      throw new CertificateError('Domain not found during authorization', domain);
    }
    
    for (const authz of authorizations) {
      const challenge = authz.challenges.find(ch => ch.type === 'http-01');
      if (!challenge) {
        throw new CertificateError('HTTP-01 challenge not found', domain);
      }
      
      // Get the key authorization for the challenge
      const keyAuthorization = await client.getChallengeKeyAuthorization(challenge);
      
      // Store the challenge data
      domainInfo.challengeToken = challenge.token;
      domainInfo.challengeKeyAuthorization = keyAuthorization;

      // ACME client type definition workaround - use compatible approach
      // First check if challenge verification is needed
      const authzUrl = authz.url;
      
      try {
        // Check if authzUrl exists and perform verification
        if (authzUrl) {
          await client.verifyChallenge(authz, challenge);
        }
        
        // Complete the challenge
        await client.completeChallenge(challenge);
        
        // Wait for validation
        await client.waitForValidStatus(challenge);
        console.log(`HTTP-01 challenge completed for ${domain}`);
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : 'Unknown challenge error';
        console.error(`Challenge error for ${domain}:`, error);
        throw new CertificateError(`Challenge verification failed: ${errorMessage}`, domain);
      }
    }
  }

  /**
   * Starts the certificate renewal timer
   */
  private startRenewalTimer(): void {
    if (this.renewalTimer) {
      clearInterval(this.renewalTimer);
    }
    
    // Convert hours to milliseconds
    const checkInterval = this.options.renewCheckIntervalHours * 60 * 60 * 1000;
    
    this.renewalTimer = setInterval(() => this.checkForRenewals(), checkInterval);
    
    // Prevent the timer from keeping the process alive
    if (this.renewalTimer.unref) {
      this.renewalTimer.unref();
    }
    
    console.log(`Certificate renewal check scheduled every ${this.options.renewCheckIntervalHours} hours`);
  }

  /**
   * Checks for certificates that need renewal
   */
  private checkForRenewals(): void {
    if (this.isShuttingDown) {
      return;
    }
    
    // Skip renewal if auto-renewal is disabled
    if (this.options.autoRenew === false) {
      console.log('Auto-renewal is disabled, skipping certificate renewal check');
      return;
    }
    
    console.log('Checking for certificates that need renewal...');
    
    const now = new Date();
    const renewThresholdMs = this.options.renewThresholdDays * 24 * 60 * 60 * 1000;
    
    for (const [domain, domainInfo] of this.domainCertificates.entries()) {
      // Skip glob patterns
      if (this.isGlobPattern(domain)) {
        continue;
      }
      
      // Skip domains with acmeMaintenance disabled
      if (!domainInfo.options.acmeMaintenance) {
        continue;
      }
      
      // Skip domains without certificates or already in renewal
      if (!domainInfo.certObtained || domainInfo.obtainingInProgress) {
        continue;
      }
      
      // Skip domains without expiry dates
      if (!domainInfo.expiryDate) {
        continue;
      }
      
      const timeUntilExpiry = domainInfo.expiryDate.getTime() - now.getTime();
      
      // Check if certificate is near expiry
      if (timeUntilExpiry <= renewThresholdMs) {
        console.log(`Certificate for ${domain} expires soon, renewing...`);
        
        const daysRemaining = Math.ceil(timeUntilExpiry / (24 * 60 * 60 * 1000));
        
        this.emit(Port80HandlerEvents.CERTIFICATE_EXPIRING, {
          domain,
          expiryDate: domainInfo.expiryDate,
          daysRemaining
        } as ICertificateExpiring);
        
        // Start renewal process
        this.obtainCertificate(domain, true).catch(err => {
          const errorMessage = err instanceof Error ? err.message : 'Unknown error';
          console.error(`Error renewing certificate for ${domain}:`, errorMessage);
        });
      }
    }
  }
  
  /**
   * Extract expiry date from certificate using a more robust approach
   * @param certificate Certificate PEM string
   * @param domain Domain for logging
   * @returns Extracted expiry date or default
   */
  private extractExpiryDateFromCertificate(certificate: string, domain: string): Date {
    try {
      // This is still using regex, but in a real implementation you would use
      // a library like node-forge or x509 to properly parse the certificate
      const matches = certificate.match(/Not After\s*:\s*(.*?)(?:\n|$)/i);
      if (matches && matches[1]) {
        const expiryDate = new Date(matches[1]);
        
        // Validate that we got a valid date
        if (!isNaN(expiryDate.getTime())) {
          console.log(`Certificate for ${domain} will expire on ${expiryDate.toISOString()}`);
          return expiryDate;
        }
      }
      
      console.warn(`Could not extract valid expiry date from certificate for ${domain}, using default`);
      return this.getDefaultExpiryDate();
    } catch (error) {
      console.warn(`Failed to extract expiry date from certificate for ${domain}, using default`);
      return this.getDefaultExpiryDate();
    }
  }
  
  /**
   * Get a default expiry date (90 days from now)
   * @returns Default expiry date
   */
  private getDefaultExpiryDate(): Date {
    return new Date(Date.now() + 90 * 24 * 60 * 60 * 1000); // 90 days default
  }
  
  /**
   * Emits a certificate event with the certificate data
   * @param eventType The event type to emit
   * @param data The certificate data
   */
  private emitCertificateEvent(eventType: Port80HandlerEvents, data: ICertificateData): void {
    this.emit(eventType, data);
  }
  
  /**
   * Gets all domains and their certificate status
   * @returns Map of domains to certificate status
   */
  public getDomainCertificateStatus(): Map<string, {
    certObtained: boolean;
    expiryDate?: Date;
    daysRemaining?: number;
    obtainingInProgress: boolean;
    lastRenewalAttempt?: Date;
  }> {
    const result = new Map<string, {
      certObtained: boolean;
      expiryDate?: Date;
      daysRemaining?: number;
      obtainingInProgress: boolean;
      lastRenewalAttempt?: Date;
    }>();
    
    const now = new Date();
    
    for (const [domain, domainInfo] of this.domainCertificates.entries()) {
      // Skip glob patterns
      if (this.isGlobPattern(domain)) continue;
      
      const status: {
        certObtained: boolean;
        expiryDate?: Date;
        daysRemaining?: number;
        obtainingInProgress: boolean;
        lastRenewalAttempt?: Date;
      } = {
        certObtained: domainInfo.certObtained,
        expiryDate: domainInfo.expiryDate,
        obtainingInProgress: domainInfo.obtainingInProgress,
        lastRenewalAttempt: domainInfo.lastRenewalAttempt
      };
      
      // Calculate days remaining if expiry date is available
      if (domainInfo.expiryDate) {
        const daysRemaining = Math.ceil(
          (domainInfo.expiryDate.getTime() - now.getTime()) / (24 * 60 * 60 * 1000)
        );
        status.daysRemaining = daysRemaining;
      }
      
      result.set(domain, status);
    }
    
    return result;
  }
  
  /**
   * Gets information about managed domains
   * @returns Array of domain information
   */
  public getManagedDomains(): Array<{
    domain: string;
    isGlobPattern: boolean;
    hasCertificate: boolean;
    hasForwarding: boolean;
    sslRedirect: boolean;
    acmeMaintenance: boolean;
  }> {
    return Array.from(this.domainCertificates.entries()).map(([domain, info]) => ({
      domain,
      isGlobPattern: this.isGlobPattern(domain),
      hasCertificate: info.certObtained,
      hasForwarding: !!info.options.forward,
      sslRedirect: info.options.sslRedirect,
      acmeMaintenance: info.options.acmeMaintenance
    }));
  }
  
  /**
   * Gets configuration details
   * @returns Current configuration
   */
  public getConfig(): Required<IPort80HandlerOptions> {
    return { ...this.options };
  }
}