/**
 * Daemon Manager for Onebox
 *
 * Handles background monitoring, metrics collection, and automatic tasks
 */

import * as plugins from '../plugins.js';
import { logger } from '../logging.js';
import { getErrorMessage } from '../utils/error.js';
import type { Onebox } from './onebox.js';

// PID file constants
const PID_FILE_PATH = '/var/run/onebox/onebox.pid';
const PID_DIR = '/var/run/onebox';
const FALLBACK_PID_DIR = `${process.env.HOME || process.cwd()}/.onebox`;
const FALLBACK_PID_FILE = `${FALLBACK_PID_DIR}/onebox.pid`;

export class OneboxDaemon {
  private oneboxRef: Onebox;
  private running = false;
  private monitoringInterval: ReturnType<typeof setInterval> | null = null;
  private statsInterval: ReturnType<typeof setInterval> | null = null;
  private metricsInterval = 60000; // 1 minute (for DB storage)
  private statsBroadcastInterval = 5000; // 5 seconds (for real-time WebSocket)
  private pidFilePath: string = PID_FILE_PATH;
  private lastDomainSync = 0; // Timestamp of last Cloudflare domain sync
  private domainSyncInterval = 6 * 60 * 60 * 1000; // 6 hours

  constructor(oneboxRef: Onebox) {
    this.oneboxRef = oneboxRef;
  }

  /**
   * Load settings from database (call after database init)
   */
  private async loadSettings(): Promise<void> {
    try {
      const customInterval = await this.oneboxRef.database.getSetting('metricsInterval');
      if (customInterval) {
        this.metricsInterval = parseInt(customInterval, 10);
      }
    } catch {
      // Database not initialized yet - use defaults
    }
  }

  /**
   * Start daemon mode (background monitoring)
   */
  async start(): Promise<void> {
    try {
      if (this.running) {
        logger.warn('Daemon already running');
        return;
      }

      logger.info('Starting Onebox daemon...');

      this.running = true;

      // Load settings from database
      await this.loadSettings();

      // Write PID file
      await this.writePidFile();

      // Start monitoring loop
      this.startMonitoring();

      // Start OpsServer (serves new UI + TypedRequest API)
      const httpPort = parseInt((await this.oneboxRef.database.getSetting('httpPort')) || '3000', 10);
      await this.oneboxRef.opsServer.start(httpPort);

      logger.success('Onebox daemon started');
      logger.info(`Web UI available at http://localhost:${httpPort}`);

      // Keep process alive
      await this.keepAlive();
    } catch (error) {
      logger.error(`Failed to start daemon: ${getErrorMessage(error)}`);
      this.running = false;
      throw error;
    }
  }

  /**
   * Stop daemon mode
   */
  async stop(): Promise<void> {
    try {
      if (!this.running) {
        return;
      }

      logger.info('Stopping Onebox daemon...');

      this.running = false;

      // Stop monitoring
      this.stopMonitoring();

      // Stop OpsServer
      await this.oneboxRef.opsServer.stop();

      // Remove PID file
      await this.removePidFile();

      logger.success('Onebox daemon stopped');
    } catch (error) {
      logger.error(`Failed to stop daemon: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Start monitoring loop
   */
  public startMonitoring(): void {
    logger.info('Starting monitoring loop...');

    this.monitoringInterval = setInterval(async () => {
      await this.monitoringTick();
    }, this.metricsInterval);

    // Start stats broadcasting loop (faster for real-time UI)
    this.statsInterval = setInterval(async () => {
      await this.broadcastStats();
    }, this.statsBroadcastInterval);

    // Run first tick immediately
    this.monitoringTick();
  }

  /**
   * Stop monitoring loop
   */
  public stopMonitoring(): void {
    if (this.monitoringInterval !== null) {
      clearInterval(this.monitoringInterval);
      this.monitoringInterval = null;
    }
    if (this.statsInterval !== null) {
      clearInterval(this.statsInterval);
      this.statsInterval = null;
    }
    logger.debug('Monitoring loops stopped');
  }

  /**
   * Single monitoring tick
   */
  private async monitoringTick(): Promise<void> {
    try {
      logger.debug('Running monitoring tick...');

      // Collect metrics for all services
      await this.collectMetrics();

      // Sync service statuses
      await this.oneboxRef.services.syncAllServiceStatuses();

      // Rehydrate CoreTraffic routes/certs if Docker replaced its service task.
      await this.reapplyReverseProxyRuntimeConfig();

      // Check SSL certificate expiration
      await this.checkSSLExpiration();

      // Process pending certificate requirements
      await this.processCertRequirements();

      // Check for certificate renewal (every tick)
      await this.checkCertificateRenewal();

      // Clean up old certificates (every tick, but cleanup has built-in 90-day threshold)
      await this.cleanupOldCertificates();

      // Sync Cloudflare domains (less frequently - every 6 hours)
      await this.syncCloudflareDomainsIfNeeded();

      // Check service health (TODO: implement health checks)

      logger.debug('Monitoring tick complete');
    } catch (error) {
      logger.error(`Monitoring tick failed: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Collect metrics for all services
   */
  private async collectMetrics(): Promise<void> {
    try {
      const services = await this.oneboxRef.services.listServices();

      for (const service of services) {
        if (service.status === 'running' && service.containerID) {
          try {
            const stats = await this.oneboxRef.docker.getContainerStats(service.containerID);

            if (stats) {
              await this.oneboxRef.database.addMetric({
                serviceId: service.id!,
                timestamp: Date.now(),
                cpuPercent: stats.cpuPercent,
                memoryUsed: stats.memoryUsed,
                memoryLimit: stats.memoryLimit,
                networkRxBytes: stats.networkRx,
                networkTxBytes: stats.networkTx,
              });
            }
          } catch (error) {
            logger.debug(`Failed to collect metrics for ${service.name}: ${getErrorMessage(error)}`);
          }
        }
      }
    } catch (error) {
      logger.error(`Failed to collect metrics: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Keep the CoreTraffic runtime aligned with Onebox's desired in-memory config.
   */
  private async reapplyReverseProxyRuntimeConfig(): Promise<void> {
    try {
      await this.oneboxRef.reverseProxy.reapplyRuntimeConfig();
    } catch (error) {
      logger.error(`Failed to reapply reverse proxy runtime config: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Broadcast stats (placeholder for future WebSocket integration via OpsServer)
   */
  private async broadcastStats(): Promise<void> {
    // Stats broadcasting via WebSocket is not yet implemented in OpsServer.
    // Metrics are still collected and stored in the DB by collectMetrics().
    // The new UI fetches stats via TypedRequests on demand.
  }

  /**
   * Check SSL certificate expiration
   */
  private async checkSSLExpiration(): Promise<void> {
    try {
      if (!this.oneboxRef.ssl.isConfigured()) {
        return;
      }

      await this.oneboxRef.ssl.renewExpiring();
    } catch (error) {
      logger.error(`Failed to check SSL expiration: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Process pending certificate requirements
   */
  private async processCertRequirements(): Promise<void> {
    try {
      await this.oneboxRef.certRequirementManager.processPendingRequirements();
    } catch (error) {
      logger.error(`Failed to process cert requirements: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Check certificates for renewal (30-day threshold)
   */
  private async checkCertificateRenewal(): Promise<void> {
    try {
      await this.oneboxRef.certRequirementManager.checkCertificateRenewal();
    } catch (error) {
      logger.error(`Failed to check certificate renewal: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Clean up old invalid certificates (90+ days old)
   */
  private async cleanupOldCertificates(): Promise<void> {
    try {
      await this.oneboxRef.certRequirementManager.cleanupOldCertificates();
    } catch (error) {
      logger.error(`Failed to cleanup old certificates: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Sync Cloudflare domains if needed (every 6 hours)
   */
  private async syncCloudflareDomainsIfNeeded(): Promise<void> {
    try {
      const now = Date.now();

      // Check if it's time to sync (every 6 hours)
      if (now - this.lastDomainSync < this.domainSyncInterval) {
        return;
      }

      if (!this.oneboxRef.cloudflareDomainSync.isConfigured()) {
        return;
      }

      await this.oneboxRef.cloudflareDomainSync.syncZones();
      this.lastDomainSync = now;
    } catch (error) {
      logger.error(`Failed to sync Cloudflare domains: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Keep process alive
   */
  private async keepAlive(): Promise<void> {
    // Set up signal handlers
    const signalHandler = () => {
      logger.info('Received shutdown signal');
      this.stop().then(() => {
        process.exit(0);
      });
    };

    process.on('SIGINT', signalHandler);
    process.on('SIGTERM', signalHandler);

    // Keep event loop alive
    while (this.running) {
      await new Promise((resolve) => setTimeout(resolve, 1000));
    }
  }

  /**
   * Get daemon status
   */
  isRunning(): boolean {
    return this.running;
  }

  /**
   * Write PID file
   */
  private async writePidFile(): Promise<void> {
    try {
      // Try primary location first
      try {
        await plugins.fs.mkdir(PID_DIR, { recursive: true });
        await plugins.fs.writeTextFile(PID_FILE_PATH, process.pid.toString());
        this.pidFilePath = PID_FILE_PATH;
        logger.debug(`PID file written: ${PID_FILE_PATH}`);
        return;
      } catch (error) {
        // Permission denied - try fallback location
        logger.debug(`Cannot write to ${PID_DIR}, using fallback location`);
      }

      // Fallback to user directory
      await plugins.fs.mkdir(FALLBACK_PID_DIR, { recursive: true });
      await plugins.fs.writeTextFile(FALLBACK_PID_FILE, process.pid.toString());
      this.pidFilePath = FALLBACK_PID_FILE;
      logger.debug(`PID file written: ${FALLBACK_PID_FILE}`);
    } catch (error) {
      logger.warn(`Failed to write PID file: ${getErrorMessage(error)}`);
      // Non-fatal - daemon can still run
    }
  }

  /**
   * Remove PID file
   */
  private async removePidFile(): Promise<void> {
    try {
      await plugins.fs.remove(this.pidFilePath);
      logger.debug(`PID file removed: ${this.pidFilePath}`);
    } catch (error) {
      // Ignore errors - file might not exist
      logger.debug(`Could not remove PID file: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Check if daemon is running
   */
  static async isDaemonRunning(): Promise<boolean> {
    const pid = await OneboxDaemon.getDaemonPid();
    if (!pid) {
      return false;
    }

    try {
      // Check if process exists
      await plugins.fs.stat(`/proc/${pid}`);
      return true;
    } catch {
      // Process doesn't exist - clean up stale PID file
      logger.debug(`Cleaning up stale PID file`);
      try {
        await plugins.fs.remove(PID_FILE_PATH);
      } catch {
        try {
          await plugins.fs.remove(FALLBACK_PID_FILE);
        } catch {
          // Ignore
        }
      }
      return false;
    }
  }

  /**
   * Get daemon PID
   */
  static async getDaemonPid(): Promise<number | null> {
    try {
      // Try primary location
      try {
        const pidText = await plugins.fs.readTextFile(PID_FILE_PATH);
        return parseInt(pidText.trim(), 10);
      } catch {
        // Try fallback location
        const pidText = await plugins.fs.readTextFile(FALLBACK_PID_FILE);
        return parseInt(pidText.trim(), 10);
      }
    } catch {
      return null;
    }
  }

  /**
   * Ensure no daemon is running
   */
  static async ensureNoDaemon(): Promise<void> {
    const running = await OneboxDaemon.isDaemonRunning();
    if (running) {
      throw new Error('Daemon is already running. Please stop it first with: onebox systemd stop');
    }
  }
}
