/**
 * Services Manager for Onebox
 *
 * Orchestrates service deployment: Docker + Nginx + DNS + SSL
 */

import type { IService, IServiceDeployOptions, IPlatformRequirements } from '../types.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import { OneboxDatabase } from './database.ts';
import { OneboxDockerManager } from './docker.ts';
import type { PlatformServicesManager } from './platform-services/index.ts';

export type TServiceUpdateProgressStep =
  | 'stopping'
  | 'pulling-image'
  | 'updating-record'
  | 'removing-container'
  | 'creating-container'
  | 'starting'
  | 'restoring-route'
  | 'syncing-gateway'
  | 'complete';

export interface IServiceUpdateProgress {
  step: TServiceUpdateProgressStep;
  message: string;
}

export interface IServiceUpdateOptions {
  onProgress?: (progressArg: IServiceUpdateProgress) => void | Promise<void>;
}

export class OneboxServicesManager {
  private oneboxRef: any; // Will be Onebox instance
  private database: OneboxDatabase;
  private docker: OneboxDockerManager;
  private autoUpdateIntervalId: number | null = null;

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

  private async broadcastServiceUpdate(
    serviceName: string,
    action: 'created' | 'updated' | 'deleted' | 'started' | 'stopped',
  ): Promise<void> {
    await this.oneboxRef.opsServer.broadcastServiceUpdate(
      serviceName,
      action,
      this.database.getServiceByName(serviceName),
    );
  }

  private async syncExternalGatewayRoute(service: IService): Promise<void> {
    if (!this.oneboxRef.externalGateway) return;
    try {
      await this.oneboxRef.externalGateway.syncServiceRoute(service);
    } catch (error) {
      logger.warn(`Failed to sync external gateway route for ${service.domain}: ${getErrorMessage(error)}`);
    }
  }

  private async deleteExternalGatewayRoute(service: Pick<IService, 'id' | 'name' | 'domain'>): Promise<void> {
    if (!this.oneboxRef.externalGateway) return;
    try {
      await this.oneboxRef.externalGateway.deleteServiceRoute(service);
    } catch (error) {
      logger.warn(`Failed to delete external gateway route for ${service.domain}: ${getErrorMessage(error)}`);
    }
  }

  private createHostedAppControlToken(): string {
    const bytes = new Uint8Array(32);
    crypto.getRandomValues(bytes);
    return Array.from(bytes).map((byteArg) => byteArg.toString(16).padStart(2, '0')).join('');
  }

  private createHostedAppRuntimeEnvVars(): {
    appInstanceId: string;
    appControlToken: string;
    envVars: Record<string, string>;
  } {
    const appInstanceId = crypto.randomUUID();
    const appControlToken = this.createHostedAppControlToken();
    const runtimeUrl =
      Deno.env.get('SERVEZONE_RUNTIME_URL') ||
      Deno.env.get('ONEBOX_RUNTIME_URL') ||
      'http://host.docker.internal:3000';
    return {
      appInstanceId,
      appControlToken,
      envVars: {
        SERVEZONE_RUNTIME_URL: runtimeUrl,
        SERVEZONE_APP_INSTANCE_ID: appInstanceId,
        SERVEZONE_APP_CONTROL_TOKEN: appControlToken,
        SERVEZONE_APP_HOST_TYPE: 'onebox',
      },
    };
  }

  /**
   * Deploy a new service (full workflow)
   */
  async deployService(options: IServiceDeployOptions): Promise<IService> {
    try {
      logger.info(`Deploying service: ${options.name}`);

      // Check if service already exists
      const existing = this.database.getServiceByName(options.name);
      if (existing) {
        throw new Error(`Service already exists: ${options.name}`);
      }

      // Handle Onebox Registry setup
      let imageToPull: string;

      if (options.useOneboxRegistry) {
        // Use onebox registry image name
        const tag = options.registryImageTag || 'latest';
        imageToPull = this.oneboxRef.registry.getImageName(options.name, tag);
      } else {
        // Use external image
        imageToPull = options.image;
      }

      // Build platform requirements
      const platformRequirements: IPlatformRequirements | undefined =
        (options.enableMongoDB || options.enableS3 || options.enableClickHouse || options.enableRedis || options.enableMariaDB)
          ? {
              mongodb: options.enableMongoDB,
              s3: options.enableS3,
              clickhouse: options.enableClickHouse,
              redis: options.enableRedis,
              mariadb: options.enableMariaDB,
            }
          : undefined;

      const hostedAppRuntime = this.createHostedAppRuntimeEnvVars();
      const requestedEnvVars = {
        ...(options.envVars || {}),
        ...hostedAppRuntime.envVars,
      };

      // Create service record in database
      const service = await this.database.createService({
        name: options.name,
        image: options.useOneboxRegistry ? imageToPull : options.image,
        registry: options.registry,
        envVars: requestedEnvVars,
        volumes: options.volumes || [],
        publishedPorts: options.publishedPorts || [],
        port: options.port,
        domain: options.domain,
        status: 'stopped',
        createdAt: Date.now(),
        updatedAt: Date.now(),
        // Onebox Registry fields
        useOneboxRegistry: options.useOneboxRegistry,
        registryRepository: options.useOneboxRegistry ? options.name : undefined,
        registryImageTag: options.registryImageTag || 'latest',
        autoUpdateOnPush: options.autoUpdateOnPush,
        imageDigest: options.imageDigest,
        // Platform requirements
        platformRequirements,
        // App Store template tracking
        appTemplateId: options.appTemplateId,
        appTemplateVersion: options.appTemplateVersion,
      });

      this.database.upsertHostedAppLifecycle({
        serviceId: service.id!,
        appInstanceId: hostedAppRuntime.appInstanceId,
        controlToken: hostedAppRuntime.appControlToken,
        hostType: 'onebox',
        state: {
          appInstanceId: hostedAppRuntime.appInstanceId,
          hostType: 'onebox',
          appName: options.name,
          publicUrl: options.domain ? `https://${options.domain}` : undefined,
          runtimeStatus: 'unknown',
        },
      });

      // Provision platform resources if needed
      let platformEnvVars: Record<string, string> = {};
      if (platformRequirements) {
        try {
          logger.info(`Provisioning platform resources for service '${options.name}'...`);
          const platformServices = this.oneboxRef.platformServices as PlatformServicesManager;
          platformEnvVars = await platformServices.provisionForService(service);
          logger.success(`Platform resources provisioned for service '${options.name}'`);
        } catch (error) {
          logger.error(`Failed to provision platform resources: ${getErrorMessage(error)}`);
          // Clean up the service record on failure
          this.database.deleteService(service.id!);
          throw error;
        }
      }

      // Merge platform and app env vars, with host lifecycle values kept authoritative.
      const mergedEnvVars = { ...platformEnvVars, ...requestedEnvVars };
      this.resolveEnvVarTemplates(mergedEnvVars, {
        ...platformEnvVars,
        SERVICE_NAME: options.name,
        SERVICE_DOMAIN: options.domain || '',
        SERVICE_PORT: String(options.port),
      });

      // Update service with merged and resolved env vars.
      if (Object.keys(mergedEnvVars).length > 0) {
        this.database.updateService(service.id!, { envVars: mergedEnvVars });
      }

      // Get updated service with merged env vars
      const serviceWithEnvVars = this.database.getServiceByName(options.name)!;

      // Pull image (skip if using onebox registry - image might not exist yet)
      if (!options.useOneboxRegistry) {
        await this.docker.pullImage(imageToPull, options.registry);
      }

      // Create container (uses the updated service with merged env vars)
      const containerID = await this.docker.createContainer(serviceWithEnvVars);

      // Update service with container ID
      this.database.updateService(service.id!, {
        containerID,
        status: 'starting',
      });

      // Start container
      await this.docker.startContainer(containerID);

      // Update status
      this.database.updateService(service.id!, { status: 'running' });

      // If domain is specified, configure nginx, DNS, and SSL
      if (options.domain) {
        logger.info(`Configuring domain: ${options.domain}`);

        // Validate domain and create CertRequirement
        try {
          // Extract base domain (e.g., "api.example.com" -> "example.com")
          const domainParts = options.domain.split('.');
          const baseDomain = domainParts.slice(-2).join('.');
          const subdomain = domainParts.length > 2 ? domainParts.slice(0, -2).join('.') : '';

          // Check if base domain exists in Domain table
          const domainRecord = this.database.getDomainByName(baseDomain);

          if (!domainRecord) {
            logger.warn(
              `Domain ${baseDomain} not found in Domain table. ` +
              `Service will deploy but certificate management may not work. ` +
              `Run Cloudflare domain sync or manually add the domain.`
            );
          } else if (domainRecord.isObsolete) {
            logger.warn(
              `Domain ${baseDomain} is marked as obsolete. ` +
              `Certificate management may not work properly.`
            );
          } else {
            // Create CertRequirement for automatic certificate management
            const now = Date.now();
            this.database.createCertRequirement({
              serviceId: service.id!,
              domainId: domainRecord.id!,
              subdomain: subdomain,
              status: 'pending',
              createdAt: now,
              updatedAt: now,
            });
            logger.info(
              `Created certificate requirement for ${options.domain} ` +
              `(domain: ${baseDomain}, subdomain: ${subdomain || 'none'})`
            );
          }
        } catch (error) {
          logger.warn(`Failed to create certificate requirement: ${getErrorMessage(error)}`);
        }

        // Configure DNS (if autoDNS is enabled)
        if (options.autoDNS !== false) {
          try {
            await this.oneboxRef.dns.addDNSRecord(options.domain);
          } catch (error) {
            logger.warn(`Failed to configure DNS for ${options.domain}: ${getErrorMessage(error)}`);
          }
        }

        // Configure reverse proxy
        try {
          await this.oneboxRef.reverseProxy.addRoute(service.id!, options.domain, options.port);
        } catch (error) {
          logger.warn(`Failed to configure reverse proxy for ${options.domain}: ${getErrorMessage(error)}`);
        }

        // Note: SSL certificates are now handled automatically by CertRequirementManager
        // which processes pending requirements created above. No direct obtainCertificate call needed.

        await this.syncExternalGatewayRoute(this.database.getServiceByName(options.name)!);
      }

      logger.success(`Service deployed successfully: ${options.name}`);

      const deployedService = this.database.getServiceByName(options.name)!;
      await this.broadcastServiceUpdate(options.name, 'created');
      return deployedService;
    } catch (error) {
      logger.error(`Failed to deploy service ${options.name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Start a service
   */
  async startService(name: string): Promise<void> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      if (!service.containerID) {
        throw new Error(`Service ${name} has no container ID`);
      }

      logger.info(`Starting service: ${name}`);

      this.database.updateService(service.id!, { status: 'starting' });

      await this.docker.startContainer(service.containerID);

      this.database.updateService(service.id!, { status: 'running' });

      // Add reverse proxy route if service has a domain
      if (service.domain) {
        try {
          await this.oneboxRef.reverseProxy.addRoute(service.id!, service.domain, service.port);
        } catch (routeError) {
          logger.warn(`Failed to add proxy route for ${service.domain}: ${getErrorMessage(routeError)}`);
        }

        await this.syncExternalGatewayRoute(this.database.getServiceByName(name)!);
      }

      logger.success(`Service started: ${name}`);
      await this.broadcastServiceUpdate(name, 'started');
    } catch (error) {
      logger.error(`Failed to start service ${name}: ${getErrorMessage(error)}`);
      this.database.updateService(
        this.database.getServiceByName(name)?.id!,
        { status: 'failed' }
      );
      await this.broadcastServiceUpdate(name, 'updated');
      throw error;
    }
  }

  /**
   * Stop a service
   */
  async stopService(name: string): Promise<void> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      if (!service.containerID) {
        throw new Error(`Service ${name} has no container ID`);
      }

      logger.info(`Stopping service: ${name}`);

      this.database.updateService(service.id!, { status: 'stopping' });

      await this.docker.stopContainer(service.containerID);

      this.database.updateService(service.id!, { status: 'stopped' });

      // Remove reverse proxy route if service has a domain
      if (service.domain) {
        await this.oneboxRef.reverseProxy.removeRoute(service.domain);
        await this.deleteExternalGatewayRoute(service);
      }

      logger.success(`Service stopped: ${name}`);
      await this.broadcastServiceUpdate(name, 'stopped');
    } catch (error) {
      logger.error(`Failed to stop service ${name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Restart a service
   */
  async restartService(name: string): Promise<void> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      if (!service.containerID) {
        throw new Error(`Service ${name} has no container ID`);
      }

      logger.info(`Restarting service: ${name}`);

      await this.docker.restartContainer(service.containerID);

      this.database.updateService(service.id!, { status: 'running' });

      logger.success(`Service restarted: ${name}`);
      await this.broadcastServiceUpdate(name, 'updated');
    } catch (error) {
      logger.error(`Failed to restart service ${name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Remove a service (full cleanup)
   */
  async removeService(name: string): Promise<void> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      logger.info(`Removing service: ${name}`);

      // Stop and remove container
      if (service.containerID) {
        try {
          await this.docker.removeContainer(service.containerID, true);
        } catch (error) {
          logger.warn(`Failed to remove container: ${getErrorMessage(error)}`);
        }
      }

      // Remove reverse proxy route
      if (service.domain) {
        try {
          await this.oneboxRef.reverseProxy.removeRoute(service.domain);
        } catch (error) {
          logger.warn(`Failed to remove reverse proxy route: ${getErrorMessage(error)}`);
        }

        await this.deleteExternalGatewayRoute(service);

        // Note: We don't remove DNS records or SSL certs automatically
        // as they might be used by other services or need manual cleanup
      }

      // Cleanup platform resources (MongoDB databases, S3 buckets, etc.)
      if (service.platformRequirements) {
        try {
          logger.info(`Cleaning up platform resources for service '${name}'...`);
          const platformServices = this.oneboxRef.platformServices as PlatformServicesManager;
          await platformServices.cleanupForService(service.id!);
          logger.success(`Platform resources cleaned up for service '${name}'`);
        } catch (error) {
          logger.warn(`Failed to cleanup platform resources: ${getErrorMessage(error)}`);
          // Continue with service deletion even if cleanup fails
        }
      }

      // Remove from database
      this.database.deleteService(service.id!);

      logger.success(`Service removed: ${name}`);
      await this.oneboxRef.opsServer.broadcastServiceUpdate(name, 'deleted');
    } catch (error) {
      logger.error(`Failed to remove service ${name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * List all services
   */
  listServices(): IService[] {
    return this.database.getAllServices();
  }

  /**
   * Get service by name
   */
  getService(name: string): IService | null {
    return this.database.getServiceByName(name);
  }

  /**
   * Get service logs
   */
  async getServiceLogs(name: string, tail = 100): Promise<string> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      if (!service.containerID) {
        throw new Error(`Service ${name} has no container ID`);
      }

      const logs = await this.docker.getContainerLogs(service.containerID, tail);

      // Debug: check what we got
      logger.debug(`getServiceLogs: logs type = ${typeof logs}, constructor = ${logs?.constructor?.name}`);
      logger.debug(`getServiceLogs: logs.stdout type = ${typeof logs.stdout}`);
      logger.debug(`getServiceLogs: logs.stdout value = ${String(logs.stdout).slice(0, 100)}`);

      // v5 API returns combined stdout/stderr with proper formatting
      return logs.stdout;
    } catch (error) {
      logger.error(`Failed to get logs for service ${name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Stream service logs (real-time)
   */
  async streamServiceLogs(
    name: string,
    callback: (line: string, isError: boolean) => void
  ): Promise<void> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      if (!service.containerID) {
        throw new Error(`Service ${name} has no container ID`);
      }

      await this.docker.streamContainerLogs(service.containerID, callback);
    } catch (error) {
      logger.error(`Failed to stream logs for service ${name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Get service metrics
   */
  async getServiceMetrics(name: string) {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      if (!service.containerID) {
        throw new Error(`Service ${name} has no container ID`);
      }

      const stats = await this.docker.getContainerStats(service.containerID);
      return stats;
    } catch (error) {
      logger.error(`Failed to get metrics for service ${name}: ${getErrorMessage(error)}`);
      return null;
    }
  }

  /**
   * Get platform resources for a service
   */
  async getServicePlatformResources(name: string) {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      if (!service.platformRequirements) {
        return [];
      }

      const platformServices = this.oneboxRef.platformServices as PlatformServicesManager;
      return await platformServices.getResourcesForService(service.id!);
    } catch (error) {
      logger.error(`Failed to get platform resources for service ${name}: ${getErrorMessage(error)}`);
      return [];
    }
  }

  /**
   * Get service status
   */
  async getServiceStatus(name: string): Promise<string> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        return 'not-found';
      }

      if (!service.containerID) {
        return service.status;
      }

      const status = await this.docker.getContainerStatus(service.containerID);
      return status;
    } catch (error) {
      logger.error(`Failed to get status for service ${name}: ${getErrorMessage(error)}`);
      return 'unknown';
    }
  }

  /**
   * Update service environment variables
   */
  async updateServiceEnv(name: string, envVars: Record<string, string>): Promise<void> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      // Update database
      this.database.updateService(service.id!, { envVars });

      // Note: Requires container restart to take effect
      logger.info(`Environment variables updated for ${name}. Restart service to apply changes.`);
    } catch (error) {
      logger.error(`Failed to update env vars for service ${name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Update service configuration (image, port, domain, env vars)
   * Recreates the container with new configuration and auto-restarts
   */
  async updateService(
    name: string,
    updates: {
      image?: string;
      registry?: string;
      port?: number;
      domain?: string;
      envVars?: Record<string, string>;
      volumes?: IService['volumes'];
      publishedPorts?: IService['publishedPorts'];
      imageDigest?: string;
      appTemplateVersion?: string;
    },
    optionsArg: IServiceUpdateOptions = {},
  ): Promise<IService> {
    try {
      const emitProgress = async (step: TServiceUpdateProgressStep, message: string) => {
        await optionsArg.onProgress?.({ step, message });
      };
      const service = this.database.getServiceByName(name);
      if (!service) {
        throw new Error(`Service not found: ${name}`);
      }

      logger.info(`Updating service: ${name}`);
      const wasRunning = service.status === 'running';
      const oldContainerID = service.containerID;
      const oldDomain = service.domain;

      // Stop the container if running
      if (wasRunning && oldContainerID) {
        logger.info(`Stopping service ${name} for updates...`);
        await emitProgress('stopping', `Stopping ${name} before updating its container`);
        try {
          await this.docker.stopContainer(oldContainerID);
        } catch (error) {
          logger.warn(`Failed to stop container: ${getErrorMessage(error)}`);
        }
      }

      // Pull new image if changed
      if (updates.image && updates.image !== service.image) {
        logger.info(`Pulling new image: ${updates.image}`);
        await emitProgress('pulling-image', `Pulling image ${updates.image}`);
        await this.docker.pullImage(updates.image, updates.registry || service.registry);
      }

      // Update service in database
      await emitProgress('updating-record', `Updating service record for ${name}`);
      const updateData: any = {
        updatedAt: Date.now(),
      };
      if (updates.image !== undefined) updateData.image = updates.image;
      if (updates.registry !== undefined) updateData.registry = updates.registry;
      if (updates.port !== undefined) updateData.port = updates.port;
      if (updates.domain !== undefined) updateData.domain = updates.domain;
      if (updates.envVars !== undefined) updateData.envVars = updates.envVars;
      if (updates.volumes !== undefined) updateData.volumes = updates.volumes;
      if (updates.publishedPorts !== undefined) updateData.publishedPorts = updates.publishedPorts;
      if (updates.imageDigest !== undefined) updateData.imageDigest = updates.imageDigest;
      if (updates.appTemplateVersion !== undefined) updateData.appTemplateVersion = updates.appTemplateVersion;

      this.database.updateService(service.id!, updateData);

      // Get updated service
      const updatedService = this.database.getServiceByName(name)!;

      // Remove old container
      if (oldContainerID) {
        await emitProgress('removing-container', `Removing old container for ${name}`);
        try {
          await this.docker.removeContainer(oldContainerID, true);
          logger.info(`Removed old container for ${name}`);
        } catch (error) {
          logger.warn(`Failed to remove old container: ${getErrorMessage(error)}`);
        }
      }

      // Create new container with updated config
      logger.info(`Creating new container for ${name}...`);
      await emitProgress('creating-container', `Creating replacement container for ${name}`);
      const containerID = await this.docker.createContainer(updatedService);
      this.database.updateService(service.id!, { containerID });

      // Update reverse proxy if domain changed
      if (updates.domain !== undefined && updates.domain !== oldDomain) {
        // Remove old route if it existed
        if (oldDomain) {
          try {
            await this.oneboxRef.reverseProxy.removeRoute(oldDomain);
          } catch (error) {
            logger.warn(`Failed to remove old reverse proxy route: ${getErrorMessage(error)}`);
          }

          await this.deleteExternalGatewayRoute({ ...service, domain: oldDomain });
        }

        // Add new route if domain specified
        if (updates.domain) {
          try {
            await this.oneboxRef.reverseProxy.addRoute(
              service.id!,
              updates.domain,
              updates.port || service.port
            );
          } catch (error) {
            logger.warn(`Failed to configure reverse proxy: ${getErrorMessage(error)}`);
          }
        }
      }

      // Restart the container if it was running
      if (wasRunning) {
        logger.info(`Starting updated service ${name}...`);
        await emitProgress('starting', `Starting updated service ${name}`);
        this.database.updateService(service.id!, { status: 'starting' });
        await this.docker.startContainer(containerID);
        this.database.updateService(service.id!, { status: 'running' });
        logger.success(`Service ${name} updated and restarted`);
      } else {
        this.database.updateService(service.id!, { status: 'stopped' });
        logger.success(`Service ${name} updated (not started)`);
      }

      const refreshedService = this.database.getServiceByName(name)!;
      if (refreshedService.domain && refreshedService.status === 'running') {
        await emitProgress('restoring-route', `Restoring route ${refreshedService.domain} -> ${refreshedService.port}`);
        try {
          await this.oneboxRef.reverseProxy.addRoute(
            refreshedService.id!,
            refreshedService.domain,
            refreshedService.port,
          );
        } catch (error) {
          logger.warn(`Failed to restore reverse proxy route for ${refreshedService.domain}: ${getErrorMessage(error)}`);
          throw error;
        }
        await emitProgress('syncing-gateway', `Syncing external gateway route for ${refreshedService.domain}`);
        await this.syncExternalGatewayRoute(refreshedService);
      }
      await emitProgress('complete', `Service ${name} update completed`);
      await this.broadcastServiceUpdate(name, 'updated');
      return refreshedService;
    } catch (error) {
      logger.error(`Failed to update service ${name}: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Sync service status from Docker
   */
  async syncServiceStatus(name: string): Promise<void> {
    try {
      const service = this.database.getServiceByName(name);
      if (!service || !service.containerID) {
        return;
      }

      const status = await this.docker.getContainerStatus(service.containerID);

      // Map Docker status to our status
      let ourStatus: IService['status'] = 'stopped';
      if (status === 'running') {
        ourStatus = 'running';
      } else if (status === 'exited' || status === 'dead') {
        ourStatus = 'stopped';
      } else if (status === 'created') {
        ourStatus = 'stopped';
      } else if (status === 'restarting') {
        ourStatus = 'starting';
      }

      // Only update and broadcast if status changed
      if (service.status !== ourStatus) {
        this.database.updateService(service.id!, { status: ourStatus });
        await this.broadcastServiceUpdate(name, 'updated');
      }
    } catch (error) {
      logger.debug(`Failed to sync status for service ${name}: ${getErrorMessage(error)}`);
    }
  }

  /**
   * Sync all service statuses from Docker
   */
  async syncAllServiceStatuses(): Promise<void> {
    const services = this.listServices();

    for (const service of services) {
      await this.syncServiceStatus(service.name);
    }
  }

  private resolveEnvVarTemplates(
    envVarsArg: Record<string, string>,
    valuesArg: Record<string, string>,
  ): void {
    for (const [key, value] of Object.entries(envVarsArg)) {
      const missingValues = new Set<string>();
      const resolvedValue = value.replace(/\$\{([A-Z0-9_]+)\}/g, (match, placeholderName) => {
        const replacement = valuesArg[placeholderName];
        if (replacement === undefined || replacement === '') {
          missingValues.add(placeholderName);
          return match;
        }
        return replacement;
      });
      if (missingValues.size > 0) {
        throw new Error(
          `Missing template value(s) for ${key}: ${Array.from(missingValues).join(', ')}`,
        );
      }
      envVarsArg[key] = resolvedValue;
    }
  }

  /**
   * Start auto-update monitoring for registry services
   * Polls every 30 seconds for digest changes and restarts services if needed
   */
  startAutoUpdateMonitoring(): void {
    // Check every 30 seconds
    this.autoUpdateIntervalId = setInterval(async () => {
      try {
        await this.checkForRegistryUpdates();
      } catch (error) {
        logger.error(`Auto-update check failed: ${getErrorMessage(error)}`);
      }
    }, 30000);

    logger.info('Auto-update monitoring started (30s interval)');
  }

  /**
   * Stop auto-update monitoring
   */
  stopAutoUpdateMonitoring(): void {
    if (this.autoUpdateIntervalId !== null) {
      clearInterval(this.autoUpdateIntervalId);
      this.autoUpdateIntervalId = null;
      logger.debug('Auto-update monitoring stopped');
    }
  }

  /**
   * Check all services using onebox registry for updates
   */
  private async checkForRegistryUpdates(): Promise<void> {
    const services = this.listServices();

    for (const service of services) {
      // Skip if not using onebox registry or auto-update is disabled
      if (!service.useOneboxRegistry || !service.autoUpdateOnPush) {
        continue;
      }

      try {
        // Get current digest from registry
        const currentDigest = await this.oneboxRef.registry.getImageDigest(
          service.registryRepository!,
          service.registryImageTag || 'latest'
        );

        // Skip if no digest found (image might not exist yet)
        if (!currentDigest) {
          continue;
        }

        // Check if digest has changed
        if (service.imageDigest && service.imageDigest !== currentDigest) {
          logger.info(
            `Digest changed for ${service.name}: ${service.imageDigest} -> ${currentDigest}`
          );

          // Update digest in database
          this.database.updateService(service.id!, {
            imageDigest: currentDigest,
          });

          // Pull new image
          const imageName = this.oneboxRef.registry.getImageName(
            service.registryRepository!,
            service.registryImageTag || 'latest'
          );

          logger.info(`Pulling updated image: ${imageName}`);
          await this.docker.pullImage(imageName);

          // Restart service
          logger.info(`Auto-restarting service: ${service.name}`);
          await this.restartService(service.name);
        } else if (!service.imageDigest) {
          // First time - just store the digest
          this.database.updateService(service.id!, {
            imageDigest: currentDigest,
          });
        }
      } catch (error) {
        logger.error(`Failed to check updates for ${service.name}: ${getErrorMessage(error)}`);
      }
    }
  }
}
