/**
 * Main Onebox coordinator class
 *
 * Coordinates all components and provides the main API
 */

import { logger } from '../logging.ts';
import { projectInfo } from '../info.ts';
import { getErrorMessage } from '../utils/error.ts';
import { hashPassword } from '../utils/auth.ts';
import { OneboxDatabase } from './database.ts';
import { OneboxDockerManager } from './docker.ts';
import { OneboxServicesManager } from './services.ts';
import { OneboxRegistriesManager } from './registries.ts';
import { OneboxReverseProxy } from './reverseproxy.ts';
import { OneboxDnsManager } from './dns.ts';
import { OneboxSslManager } from './ssl.ts';
import { OneboxDaemon } from './daemon.ts';
import { OneboxSystemd } from './systemd.ts';
import { CloudflareDomainSync } from './cloudflare-sync.ts';
import { CertRequirementManager } from './cert-requirement-manager.ts';
import { RegistryManager } from './registry.ts';
import { PlatformServicesManager } from './platform-services/index.ts';
import { AppStoreManager } from './appstore.ts';
import { ProxyLogReceiver } from './proxy-log-receiver.ts';
import { BackupManager } from './backup-manager.ts';
import { BackupScheduler } from './backup-scheduler.ts';
import { ExternalGatewayManager } from './external-gateway.ts';
import { ManagedDcRouterManager } from './managed-dcrouter.ts';
import { OneboxUpdateManager } from './update-manager.ts';
import { OpsServer } from '../opsserver/index.ts';

export class Onebox {
  public database: OneboxDatabase;
  public docker: OneboxDockerManager;
  public services: OneboxServicesManager;
  public registries: OneboxRegistriesManager;
  public reverseProxy: OneboxReverseProxy;
  public dns: OneboxDnsManager;
  public ssl: OneboxSslManager;
  public daemon: OneboxDaemon;
  public systemd: OneboxSystemd;
  public cloudflareDomainSync: CloudflareDomainSync;
  public certRequirementManager: CertRequirementManager;
  public registry: RegistryManager;
  public platformServices: PlatformServicesManager;
  public appStore: AppStoreManager;
  public proxyLogReceiver: ProxyLogReceiver;
  public backupManager: BackupManager;
  public backupScheduler: BackupScheduler;
  public managedDcRouter: ManagedDcRouterManager;
  public externalGateway: ExternalGatewayManager;
  public updateManager: OneboxUpdateManager;
  public opsServer: OpsServer;

  private initialized = false;

  constructor() {
    // Initialize database first
    this.database = new OneboxDatabase();

    // Initialize managers (passing reference to main Onebox instance)
    this.docker = new OneboxDockerManager();
    this.services = new OneboxServicesManager(this);
    this.registries = new OneboxRegistriesManager(this);
    this.reverseProxy = new OneboxReverseProxy(this);
    this.dns = new OneboxDnsManager(this);
    this.ssl = new OneboxSslManager(this);
    this.daemon = new OneboxDaemon(this);
    this.systemd = new OneboxSystemd();
    this.registry = new RegistryManager({
      dataDir: './.nogit/registry-data',
      port: 4000,
      baseUrl: 'localhost:3000',
    });

    // Initialize domain management
    this.cloudflareDomainSync = new CloudflareDomainSync(this.database);
    this.certRequirementManager = new CertRequirementManager(this.database, this.ssl);

    // Initialize platform services manager
    this.platformServices = new PlatformServicesManager(this);

    // Initialize App Store manager
    this.appStore = new AppStoreManager(this);

    // Initialize reverse proxy log receiver
    this.proxyLogReceiver = new ProxyLogReceiver(9999);

    // Initialize Backup manager
    this.backupManager = new BackupManager(this);

    // Initialize Backup scheduler
    this.backupScheduler = new BackupScheduler(this);

    // Initialize optional dcrouter gateway integration
    this.managedDcRouter = new ManagedDcRouterManager(this);
    this.externalGateway = new ExternalGatewayManager(this);
    this.updateManager = new OneboxUpdateManager();

    // Initialize OpsServer (TypedRequest-based server)
    this.opsServer = new OpsServer(this);
  }

  /**
   * Initialize all components
   */
  async init(): Promise<void> {
    try {
      logger.info('Initializing Onebox...');

      // Initialize database
      await this.database.init();

      // Ensure default admin user exists
      await this.ensureDefaultUser();

      // Initialize Docker
      await this.docker.init();

      try {
        await this.managedDcRouter.prepareGatewaySettings();
      } catch (error) {
        logger.warn(`Managed dcrouter settings preparation failed: ${getErrorMessage(error)}`);
      }

      if (this.managedDcRouter.getMode() !== 'managed') {
        try {
          await this.managedDcRouter.stop();
        } catch (error) {
          logger.warn(`Failed to stop inactive managed dcrouter: ${getErrorMessage(error)}`);
        }
      }

      // Start proxy log receiver before reverse proxy startup.
      try {
        await this.proxyLogReceiver.start();
      } catch (error) {
        logger.warn(`Failed to start proxy log receiver: ${getErrorMessage(error)}`);
      }

      // Initialize Reverse Proxy
      await this.reverseProxy.init();

      // Load routes and certificates for reverse proxy
      await this.reverseProxy.reloadRoutes();
      await this.reverseProxy.reloadCertificates();

      // Start HTTP reverse proxy (non-critical - don't fail init if ports are busy)
      // Use 8080/8443 in dev mode to avoid permission issues
      const isDev = Deno.env.get('ONEBOX_DEV') === 'true' || Deno.args.includes('--ephemeral');
      const isManagedDcRouter = this.managedDcRouter.getMode() === 'managed';
      const httpPort = isDev || isManagedDcRouter ? 8080 : 80;
      const httpsPort = isDev || isManagedDcRouter ? 8443 : 443;

      try {
        await this.reverseProxy.startHttp(httpPort);
      } catch (error) {
        logger.warn(`Failed to start HTTP reverse proxy: ${getErrorMessage(error)}`);
      }

      // Start HTTPS reverse proxy if certificates are available
      try {
        await this.reverseProxy.startHttps(httpsPort);
      } catch (error) {
        logger.warn(`Failed to start HTTPS reverse proxy: ${getErrorMessage(error)}`);
      }

      // Initialize DNS (non-critical)
      try {
        await this.dns.init();
      } catch (error) {
        logger.warn('DNS initialization failed - DNS features will be disabled');
      }

      // Initialize SSL (non-critical)
      try {
        await this.ssl.init();
      } catch (error) {
        logger.warn('SSL initialization failed - SSL features will be limited');
      }

      // Initialize Cloudflare domain sync (non-critical)
      try {
        await this.cloudflareDomainSync.init();
      } catch (error) {
        logger.warn('Cloudflare domain sync initialization failed - domain sync will be limited');
      }

      // Initialize managed local dcrouter before syncing delegated routes.
      try {
        await this.managedDcRouter.init();
      } catch (error) {
        logger.warn('Managed dcrouter initialization failed - local gateway sync will be disabled');
        logger.warn(`Error: ${getErrorMessage(error)}`);
      }

      // Initialize external dcrouter gateway (non-critical)
      try {
        await this.externalGateway.init();
      } catch (error) {
        logger.warn('External dcrouter gateway initialization failed - edge sync will be disabled');
        logger.warn(`Error: ${getErrorMessage(error)}`);
      }

      // Initialize Onebox Registry (non-critical)
      try {
        await this.registry.init();
      } catch (error) {
        logger.warn('Onebox Registry initialization failed - local registry will be disabled');
        logger.warn(`Error: ${getErrorMessage(error)}`);
      }

      // Initialize Platform Services (non-critical)
      try {
        await this.platformServices.init();
      } catch (error) {
        logger.warn('Platform services initialization failed - MongoDB/S3 features will be limited');
        logger.warn(`Error: ${getErrorMessage(error)}`);
      }

      // Initialize App Store (non-critical)
      try {
        await this.appStore.init();
      } catch (error) {
        logger.warn('App Store initialization failed - app templates will be unavailable until reconnected');
        logger.warn(`Error: ${getErrorMessage(error)}`);
      }

      // Login to all registries
      await this.registries.loginToAllRegistries();

      // Start auto-update monitoring for registry services
      this.services.startAutoUpdateMonitoring();

      // Initialize BackupManager (containerarchive repository, non-critical)
      try {
        await this.backupManager.init();
      } catch (error) {
        logger.warn('BackupManager initialization failed - backups will be limited');
        logger.warn(`Error: ${getErrorMessage(error)}`);
      }

      // Initialize Backup Scheduler (non-critical)
      try {
        await this.backupScheduler.init();
      } catch (error) {
        logger.warn('Backup scheduler initialization failed - scheduled backups will be disabled');
        logger.warn(`Error: ${getErrorMessage(error)}`);
      }

      this.initialized = true;
      logger.success('Onebox initialized successfully');
    } catch (error) {
      logger.error(`Failed to initialize Onebox: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Ensure default admin user exists
   */
  private async ensureDefaultUser(): Promise<void> {
    try {
      const adminUsername = Deno.env.get('ONEBOX_ADMIN_USERNAME') || 'admin';
      const adminUser = this.database.getUserByUsername(adminUsername);

      if (!adminUser) {
        logger.info(`Creating initial admin user ${adminUsername}...`);

        const configuredPassword = Deno.env.get('ONEBOX_ADMIN_PASSWORD');
        const initialPassword = configuredPassword || crypto.randomUUID().replaceAll('-', '');
        const passwordHash = await hashPassword(initialPassword);

        await this.database.createUser({
          username: adminUsername,
          passwordHash,
          role: 'admin',
          createdAt: Date.now(),
          updatedAt: Date.now(),
        });

        if (configuredPassword) {
          logger.warn(`Initial admin user created from ONEBOX_ADMIN_PASSWORD: ${adminUsername}`);
        } else {
          logger.warn(`Initial admin user created: ${adminUsername}`);
          logger.warn(`Generated one-time admin password: ${initialPassword}`);
        }
        logger.warn('Change the initial admin password immediately.');
      }
    } catch (error) {
      logger.error(`Failed to create default user: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Check if Onebox is initialized
   */
  isInitialized(): boolean {
    return this.initialized;
  }

  /**
   * Get system status
   */
  async getSystemStatus() {
    try {
      const dockerRunning = await this.docker.isDockerRunning();
      const proxyStatus = this.reverseProxy.getStatus();
      const dnsConfigured = this.dns.isConfigured();
      const sslConfigured = this.ssl.isConfigured();
      const oneboxUpdate = await this.updateManager.getUpdateStatus();

      const services = this.services.listServices();
      const runningServices = services.filter((s) => s.status === 'running').length;
      const totalServices = services.length;

      // Get platform services status with resource counts
      const platformServices = this.platformServices.getAllPlatformServices();
      const providers = this.platformServices.getAllProviders();
      const platformServicesStatus = providers.map((provider) => {
        const service = platformServices.find((s) => s.type === provider.type);
        // For SmartProxy, check actual runtime status since it starts without a DB record
        let status = service?.status || 'not-deployed';
        if (provider.type === 'smartproxy') {
          status = proxyStatus.http.running ? 'running' : 'stopped';
        }
        // Count resources for this platform service
        const resourceCount = service?.id
          ? this.database.getPlatformResourcesByPlatformService(service.id).length
          : 0;
        return {
          type: provider.type,
          displayName: provider.displayName,
          status,
          resourceCount,
        };
      });

      // Get certificate health summary
      const certificates = this.ssl.listCertificates();
      const now = Date.now();
      const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000;
      let validCount = 0;
      let expiringCount = 0;
      let expiredCount = 0;
      const expiringDomains: { domain: string; daysRemaining: number }[] = [];

      for (const cert of certificates) {
        if (cert.expiryDate <= now) {
          expiredCount++;
        } else if (cert.expiryDate <= now + thirtyDaysMs) {
          expiringCount++;
          const daysRemaining = Math.floor((cert.expiryDate - now) / (24 * 60 * 60 * 1000));
          expiringDomains.push({ domain: cert.domain, daysRemaining });
        } else {
          validCount++;
        }
      }

      // Sort expiring domains by days remaining (ascending)
      expiringDomains.sort((a, b) => a.daysRemaining - b.daysRemaining);

      // Aggregate resource usage across all running service containers
      let totalCpu = 0;
      let totalMemoryUsed = 0;
      let totalMemoryLimit = 0;
      let totalNetworkIn = 0;
      let totalNetworkOut = 0;

      if (dockerRunning) {
        const allServices = this.services.listServices();
        const runningUserServices = allServices.filter((s) => s.status === 'running' && s.containerID);
        logger.debug(`Resource stats: ${runningUserServices.length} running user services`);

        const statsPromises = runningUserServices
          .map((s) => {
            logger.debug(`Fetching stats for user service: ${s.name} (${s.containerID})`);
            return this.docker.getContainerStats(s.containerID!).catch((err) => {
              logger.debug(`Stats failed for ${s.name}: ${(err as Error).message}`);
              return null;
            });
          });

        // Also get stats for platform service containers
        const allPlatformServices = this.platformServices.getAllPlatformServices();
        const runningPlatformServices = allPlatformServices.filter((s) => s.status === 'running' && s.containerId);
        logger.debug(`Resource stats: ${runningPlatformServices.length} running platform services`);

        const platformStatsPromises = runningPlatformServices
          .map((s) => {
            logger.debug(`Fetching stats for platform service: ${s.type} (${s.containerId})`);
            return this.docker.getContainerStats(s.containerId!).catch((err) => {
              logger.debug(`Stats failed for ${s.type}: ${(err as Error).message}`);
              return null;
            });
          });

        const allStats = await Promise.all([...statsPromises, ...platformStatsPromises]);
        let successCount = 0;
        for (const stats of allStats) {
          if (stats) {
            successCount++;
            totalCpu += stats.cpuPercent;
            totalMemoryUsed += stats.memoryUsed;
            totalMemoryLimit = Math.max(totalMemoryLimit, stats.memoryLimit);
            totalNetworkIn += stats.networkRx;
            totalNetworkOut += stats.networkTx;
          }
        }
        logger.debug(`Resource stats: ${successCount}/${allStats.length} containers returned stats. CPU: ${totalCpu}, Mem: ${totalMemoryUsed}`);
      }

      return {
        onebox: {
          version: projectInfo.version,
          update: oneboxUpdate,
        },
        docker: {
          running: dockerRunning,
          version: dockerRunning ? await this.docker.getDockerVersion() : null,
          cpuUsage: Math.round(totalCpu * 10) / 10,
          memoryUsage: totalMemoryUsed,
          memoryTotal: totalMemoryLimit,
          networkIn: totalNetworkIn,
          networkOut: totalNetworkOut,
        },
        reverseProxy: proxyStatus,
        dns: {
          configured: dnsConfigured,
        },
        ssl: {
          configured: sslConfigured,
          certificateCount: this.ssl.listCertificates().length,
        },
        services: {
          total: totalServices,
          running: runningServices,
          stopped: totalServices - runningServices,
        },
        platformServices: platformServicesStatus,
        certificateHealth: {
          valid: validCount,
          expiringSoon: expiringCount,
          expired: expiredCount,
          expiringDomains: expiringDomains.slice(0, 5), // Top 5 expiring
        },
      };
    } catch (error) {
      logger.error(`Failed to get system status: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Start OpsServer (TypedRequest-based, serves new UI)
   */
  async startHttpServer(port?: number): Promise<void> {
    await this.opsServer.start(port || 3000);
  }

  /**
   * Stop OpsServer
   */
  async stopHttpServer(): Promise<void> {
    await this.opsServer.stop();
  }

  /**
   * Shutdown Onebox gracefully
   */
  async shutdown(): Promise<void> {
    try {
      logger.info('Shutting down Onebox...');

      // Stop auto-update monitoring
      this.services.stopAutoUpdateMonitoring();

      // Stop backup scheduler
      await this.backupScheduler.stop();

      // Stop daemon if running
      await this.daemon.stop();

      // Stop OpsServer if running
      await this.opsServer.stop();

      // Stop reverse proxy if running
      await this.reverseProxy.stop();

      // Stop proxy log receiver
      await this.proxyLogReceiver.stop();

      // Stop built-in registry and backing smartstorage server
      await this.registry.stop();

      // Close backup archive
      await this.backupManager.close();

      // Release Docker client resources after all Docker-backed managers stopped.
      await this.docker.stop();

      // Close database
      this.database.close();

      logger.success('Onebox shutdown complete');
    } catch (error) {
      logger.error(`Error during shutdown: ${getErrorMessage(error)}`);
    }
  }
}
