/**
 * HTTP Server for Onebox
 *
 * Serves REST API and Angular UI
 */

import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
import { getErrorMessage } from '../utils/error.ts';
import type { Onebox } from './onebox.ts';
import type {
  IApiResponse,
  ICreateRegistryTokenRequest,
  IRegistryTokenView,
  TPlatformServiceType,
  IContainerStats,
  IBackupScheduleCreate,
  IBackupScheduleUpdate,
} from '../types.ts';

export class OneboxHttpServer {
  private oneboxRef: Onebox;
  private server: Deno.HttpServer | null = null;
  private port = 3000;
  private wsClients: Set<WebSocket> = new Set();

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

  /**
   * Start HTTP server
   */
  async start(port?: number): Promise<void> {
    try {
      if (this.server) {
        logger.warn('HTTP server already running');
        return;
      }

      this.port = port || 3000;

      logger.info(`Starting HTTP server on port ${this.port}...`);

      this.server = Deno.serve({ port: this.port }, (req) => this.handleRequest(req));

      logger.success(`HTTP server started on http://localhost:${this.port}`);
    } catch (error) {
      logger.error(`Failed to start HTTP server: ${getErrorMessage(error)}`);
      throw error;
    }
  }

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

      logger.info('Stopping HTTP server...');
      await this.server.shutdown();
      this.server = null;
      logger.success('HTTP server stopped');
    } catch (error) {
      logger.error(`Failed to stop HTTP server: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  /**
   * Handle HTTP request
   */
  private async handleRequest(req: Request): Promise<Response> {
    const url = new URL(req.url);
    const path = url.pathname;

    logger.debug(`${req.method} ${path}`);

    try {
      // WebSocket upgrade
      if (path === '/api/ws' && req.headers.get('upgrade') === 'websocket') {
        return this.handleWebSocketUpgrade(req);
      }

      // Log streaming WebSocket
      if (path.startsWith('/api/services/') && path.endsWith('/logs/stream') && req.headers.get('upgrade') === 'websocket') {
        const serviceName = path.split('/')[3];
        return this.handleLogStreamUpgrade(req, serviceName);
      }

      // Platform service log streaming WebSocket
      if (path.startsWith('/api/platform-services/') && path.endsWith('/logs/stream') && req.headers.get('upgrade') === 'websocket') {
        const platformType = path.split('/')[3];
        return this.handlePlatformLogStreamUpgrade(req, platformType);
      }

      // Network access logs WebSocket
      if (path === '/api/network/logs/stream' && req.headers.get('upgrade') === 'websocket') {
        return this.handleNetworkLogStreamUpgrade(req, new URL(req.url));
      }

      // Docker Registry v2 Token endpoint (for OCI authentication)
      if (path === '/v2/token') {
        return await this.handleRegistryTokenRequest(req, url);
      }

      // Docker Registry v2 API (no auth required - registry handles it)
      if (path.startsWith('/v2/')) {
        return await this.oneboxRef.registry.handleRequest(req);
      }

      // API routes
      if (path.startsWith('/api/')) {
        return await this.handleApiRequest(req, path);
      }

      // Serve Angular UI
      return await this.serveStaticFile(path);
    } catch (error) {
      logger.error(`Request error: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) }, 500);
    }
  }

  /**
   * Serve static files from ui/dist
   */
  private async serveStaticFile(path: string): Promise<Response> {
    try {
      // Default to index.html for root and non-file paths
      let filePath = path === '/' ? '/index.html' : path;

      // For Angular routing - serve index.html for non-asset paths
      if (!filePath.includes('.') && filePath !== '/index.html') {
        filePath = '/index.html';
      }

      const fullPath = `./ui/dist/ui/browser${filePath}`;

      // Read file
      const file = await Deno.readFile(fullPath);

      // Determine content type
      const contentType = this.getContentType(filePath);
      // Prevent stale bundles in dev (no hashed filenames) while allowing long-lived caching for hashed prod assets
      const isHashedAsset = /\.[a-f0-9]{8,}\./i.test(filePath);
      const cacheControl =
        filePath === '/index.html' || !isHashedAsset
          ? 'no-cache'
          : 'public, max-age=31536000, immutable';

      return new Response(file, {
        headers: {
          'Content-Type': contentType,
          'Cache-Control': cacheControl,
        },
      });
    } catch (error) {
      // File not found - serve index.html for Angular routing
      if (error instanceof Deno.errors.NotFound) {
        try {
          const indexFile = await Deno.readFile('./ui/dist/ui/browser/index.html');
          return new Response(indexFile, {
            headers: {
              'Content-Type': 'text/html',
              'Cache-Control': 'no-cache',
            },
          });
        } catch {
          return new Response('UI not built. Run: cd ui && npm run build', {
            status: 404,
            headers: { 'Content-Type': 'text/plain' },
          });
        }
      }

      return new Response('File not found', {
        status: 404,
        headers: { 'Content-Type': 'text/plain' },
      });
    }
  }

  /**
   * Get content type for file
   */
  private getContentType(path: string): string {
    const ext = path.split('.').pop()?.toLowerCase();

    const mimeTypes: Record<string, string> = {
      'html': 'text/html',
      'css': 'text/css',
      'js': 'application/javascript',
      'json': 'application/json',
      'png': 'image/png',
      'jpg': 'image/jpeg',
      'jpeg': 'image/jpeg',
      'gif': 'image/gif',
      'svg': 'image/svg+xml',
      'ico': 'image/x-icon',
      'woff': 'font/woff',
      'woff2': 'font/woff2',
      'ttf': 'font/ttf',
      'eot': 'application/vnd.ms-fontobject',
    };

    return mimeTypes[ext || ''] || 'application/octet-stream';
  }

  /**
   * Handle API requests
   */
  private async handleApiRequest(req: Request, path: string): Promise<Response> {
    const method = req.method;

    // Auth check (simplified - should use proper JWT middleware)
    // Skip auth for login endpoint
    if (path !== '/api/auth/login') {
      const authHeader = req.headers.get('Authorization');
      if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return this.jsonResponse({ success: false, error: 'Unauthorized' }, 401);
      }
    }

    // Route to appropriate handler
    if (path === '/api/auth/login' && method === 'POST') {
      return await this.handleLoginRequest(req);
    } else if (path === '/api/status' && method === 'GET') {
      return await this.handleStatusRequest();
    } else if (path === '/api/settings' && method === 'GET') {
      return await this.handleGetSettingsRequest();
    } else if (path === '/api/settings' && (method === 'PUT' || method === 'POST')) {
      return await this.handleUpdateSettingsRequest(req);
    } else if (path === '/api/services' && method === 'GET') {
      return await this.handleListServicesRequest();
    } else if (path === '/api/services' && method === 'POST') {
      return await this.handleDeployServiceRequest(req);
    } else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'GET') {
      const name = path.split('/').pop()!;
      return await this.handleGetServiceRequest(name);
    } else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'PUT') {
      const name = path.split('/').pop()!;
      return await this.handleUpdateServiceRequest(name, req);
    } else if (path.match(/^\/api\/services\/[^/]+$/) && method === 'DELETE') {
      const name = path.split('/').pop()!;
      return await this.handleDeleteServiceRequest(name);
    } else if (path.match(/^\/api\/services\/[^/]+\/start$/) && method === 'POST') {
      const name = path.split('/')[3];
      return await this.handleStartServiceRequest(name);
    } else if (path.match(/^\/api\/services\/[^/]+\/stop$/) && method === 'POST') {
      const name = path.split('/')[3];
      return await this.handleStopServiceRequest(name);
    } else if (path.match(/^\/api\/services\/[^/]+\/restart$/) && method === 'POST') {
      const name = path.split('/')[3];
      return await this.handleRestartServiceRequest(name);
    } else if (path.match(/^\/api\/services\/[^/]+\/logs$/) && method === 'GET') {
      const name = path.split('/')[3];
      return await this.handleGetLogsRequest(name);
    } else if (path.match(/^\/api\/services\/[^/]+\/stats$/) && method === 'GET') {
      const name = path.split('/')[3];
      return await this.handleGetServiceStatsRequest(name);
    } else if (path.match(/^\/api\/services\/[^/]+\/metrics$/) && method === 'GET') {
      const name = path.split('/')[3];
      const limit = new URL(req.url).searchParams.get('limit');
      return await this.handleGetServiceMetricsRequest(name, limit ? parseInt(limit, 10) : 60);
    } else if (path === '/api/ssl/obtain' && method === 'POST') {
      return await this.handleObtainCertificateRequest(req);
    } else if (path === '/api/ssl/list' && method === 'GET') {
      return await this.handleListCertificatesRequest();
    } else if (path.match(/^\/api\/ssl\/[^/]+$/) && method === 'GET') {
      const domain = path.split('/').pop()!;
      return await this.handleGetCertificateRequest(domain);
    } else if (path.match(/^\/api\/ssl\/[^/]+\/renew$/) && method === 'POST') {
      const domain = path.split('/')[3];
      return await this.handleRenewCertificateRequest(domain);
    } else if (path === '/api/domains' && method === 'GET') {
      return await this.handleGetDomainsRequest();
    } else if (path === '/api/domains/sync' && method === 'POST') {
      return await this.handleSyncDomainsRequest();
    } else if (path.match(/^\/api\/domains\/[^/]+$/) && method === 'GET') {
      const domainName = path.split('/').pop()!;
      return await this.handleGetDomainDetailRequest(domainName);
    } else if (path === '/api/dns' && method === 'GET') {
      return await this.handleGetDnsRecordsRequest();
    } else if (path === '/api/dns' && method === 'POST') {
      return await this.handleCreateDnsRecordRequest(req);
    } else if (path.match(/^\/api\/dns\/[^/]+$/) && method === 'DELETE') {
      const domain = path.split('/').pop()!;
      return await this.handleDeleteDnsRecordRequest(domain);
    } else if (path === '/api/dns/sync' && method === 'POST') {
      return await this.handleSyncDnsRecordsRequest();
    } else if (path.match(/^\/api\/registry\/tags\/[^/]+$/)) {
      const serviceName = path.split('/').pop()!;
      return await this.handleGetRegistryTagsRequest(serviceName);
    } else if (path === '/api/registry/tokens' && method === 'GET') {
      return await this.handleListRegistryTokensRequest(req);
    } else if (path === '/api/registry/tokens' && method === 'POST') {
      return await this.handleCreateRegistryTokenRequest(req);
    } else if (path.match(/^\/api\/registry\/tokens\/\d+$/) && method === 'DELETE') {
      const tokenId = Number(path.split('/').pop());
      return await this.handleDeleteRegistryTokenRequest(tokenId);
    // Platform Services endpoints
    } else if (path === '/api/platform-services' && method === 'GET') {
      return await this.handleListPlatformServicesRequest();
    } else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq|caddy|clickhouse)$/) && method === 'GET') {
      const type = path.split('/').pop()! as TPlatformServiceType;
      return await this.handleGetPlatformServiceRequest(type);
    } else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq|caddy|clickhouse)\/start$/) && method === 'POST') {
      const type = path.split('/')[3] as TPlatformServiceType;
      return await this.handleStartPlatformServiceRequest(type);
    } else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq|caddy|clickhouse)\/stop$/) && method === 'POST') {
      const type = path.split('/')[3] as TPlatformServiceType;
      return await this.handleStopPlatformServiceRequest(type);
    } else if (path.match(/^\/api\/platform-services\/(mongodb|minio|redis|postgresql|rabbitmq|caddy|clickhouse)\/stats$/) && method === 'GET') {
      const type = path.split('/')[3] as TPlatformServiceType;
      return await this.handleGetPlatformServiceStatsRequest(type);
    } else if (path.match(/^\/api\/services\/[^/]+\/platform-resources$/) && method === 'GET') {
      const serviceName = path.split('/')[3];
      return await this.handleGetServicePlatformResourcesRequest(serviceName);
    // Network endpoints
    } else if (path === '/api/network/targets' && method === 'GET') {
      return await this.handleGetNetworkTargetsRequest();
    } else if (path === '/api/network/stats' && method === 'GET') {
      return await this.handleGetNetworkStatsRequest();
    } else if (path === '/api/network/traffic-stats' && method === 'GET') {
      return await this.handleGetTrafficStatsRequest(new URL(req.url));
    // Backup endpoints
    } else if (path === '/api/backups' && method === 'GET') {
      return await this.handleListBackupsRequest();
    } else if (path.match(/^\/api\/services\/[^/]+\/backups$/) && method === 'GET') {
      const serviceName = path.split('/')[3];
      return await this.handleListServiceBackupsRequest(serviceName);
    } else if (path.match(/^\/api\/services\/[^/]+\/backup$/) && method === 'POST') {
      const serviceName = path.split('/')[3];
      return await this.handleCreateBackupRequest(serviceName);
    } else if (path.match(/^\/api\/backups\/\d+$/) && method === 'GET') {
      const backupId = Number(path.split('/').pop());
      return await this.handleGetBackupRequest(backupId);
    } else if (path.match(/^\/api\/backups\/\d+\/download$/) && method === 'GET') {
      const backupId = Number(path.split('/')[3]);
      return await this.handleDownloadBackupRequest(backupId);
    } else if (path.match(/^\/api\/backups\/\d+$/) && method === 'DELETE') {
      const backupId = Number(path.split('/').pop());
      return await this.handleDeleteBackupRequest(backupId);
    } else if (path === '/api/backups/restore' && method === 'POST') {
      return await this.handleRestoreBackupRequest(req);
    } else if (path === '/api/backups/import' && method === 'POST') {
      return await this.handleImportBackupRequest(req);
    } else if (path === '/api/settings/backup-password' && method === 'POST') {
      return await this.handleSetBackupPasswordRequest(req);
    } else if (path === '/api/settings/backup-password' && method === 'GET') {
      return await this.handleCheckBackupPasswordRequest();
    // Backup Schedule endpoints
    } else if (path === '/api/backup-schedules' && method === 'GET') {
      return await this.handleListBackupSchedulesRequest();
    } else if (path === '/api/backup-schedules' && method === 'POST') {
      return await this.handleCreateBackupScheduleRequest(req);
    } else if (path.match(/^\/api\/backup-schedules\/\d+$/) && method === 'GET') {
      const scheduleId = Number(path.split('/').pop());
      return await this.handleGetBackupScheduleRequest(scheduleId);
    } else if (path.match(/^\/api\/backup-schedules\/\d+$/) && method === 'PUT') {
      const scheduleId = Number(path.split('/').pop());
      return await this.handleUpdateBackupScheduleRequest(scheduleId, req);
    } else if (path.match(/^\/api\/backup-schedules\/\d+$/) && method === 'DELETE') {
      const scheduleId = Number(path.split('/').pop());
      return await this.handleDeleteBackupScheduleRequest(scheduleId);
    } else if (path.match(/^\/api\/backup-schedules\/\d+\/trigger$/) && method === 'POST') {
      const scheduleId = Number(path.split('/')[3]);
      return await this.handleTriggerBackupScheduleRequest(scheduleId);
    } else if (path.match(/^\/api\/services\/[^/]+\/backup-schedules$/) && method === 'GET') {
      const serviceName = path.split('/')[3];
      return await this.handleListServiceBackupSchedulesRequest(serviceName);
    } else {
      return this.jsonResponse({ success: false, error: 'Not found' }, 404);
    }
  }

  // API Handlers

  private async handleLoginRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json();
      const { username, password } = body;

      logger.info(`Login attempt for user: ${username}`);

      if (!username || !password) {
        return this.jsonResponse(
          { success: false, error: 'Username and password required' },
          400
        );
      }

      // Get user from database
      const user = this.oneboxRef.database.getUserByUsername(username);

      if (!user) {
        logger.info(`User not found: ${username}`);
        return this.jsonResponse({ success: false, error: 'Invalid credentials' }, 401);
      }

      logger.info(`User found: ${username}, checking password...`);

      // Verify password (simple base64 comparison for now)
      const passwordHash = btoa(password);
      logger.info(`Password hash: ${passwordHash}, stored hash: ${user.passwordHash}`);

      if (passwordHash !== user.passwordHash) {
        logger.info(`Password mismatch for user: ${username}`);
        return this.jsonResponse({ success: false, error: 'Invalid credentials' }, 401);
      }

      // Generate simple token (in production, use proper JWT)
      const token = btoa(`${user.username}:${Date.now()}`);

      return this.jsonResponse({
        success: true,
        data: {
          token,
          user: {
            username: user.username,
            role: user.role,
          },
        },
      });
    } catch (error) {
      logger.error(`Login error: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: 'Login failed' }, 500);
    }
  }

  private async handleStatusRequest(): Promise<Response> {
    try {
      const status = await this.oneboxRef.getSystemStatus();
      return this.jsonResponse({ success: true, data: status });
    } catch (error) {
      logger.error(`Failed to get system status: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get system status' }, 500);
    }
  }

  private async handleListServicesRequest(): Promise<Response> {
    try {
      const services = this.oneboxRef.services.listServices();
      return this.jsonResponse({ success: true, data: services });
    } catch (error) {
      logger.error(`Failed to list services: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to list services' }, 500);
    }
  }

  private async handleDeployServiceRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json();
      const service = await this.oneboxRef.services.deployService(body);

      // Broadcast service created
      this.broadcastServiceUpdate(service.name, 'created', service);

      return this.jsonResponse({ success: true, data: service });
    } catch (error) {
      logger.error(`Failed to deploy service: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to deploy service' }, 500);
    }
  }

  private async handleGetServiceRequest(name: string): Promise<Response> {
    try {
      const service = this.oneboxRef.services.getService(name);
      if (!service) {
        return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
      }
      return this.jsonResponse({ success: true, data: service });
    } catch (error) {
      logger.error(`Failed to get service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get service' }, 500);
    }
  }

  private async handleUpdateServiceRequest(name: string, req: Request): Promise<Response> {
    try {
      const body = await req.json();
      const updates: {
        image?: string;
        registry?: string;
        port?: number;
        domain?: string;
        envVars?: Record<string, string>;
      } = {};

      // Extract valid update fields
      if (body.image !== undefined) updates.image = body.image;
      if (body.registry !== undefined) updates.registry = body.registry;
      if (body.port !== undefined) updates.port = body.port;
      if (body.domain !== undefined) updates.domain = body.domain;
      if (body.envVars !== undefined) updates.envVars = body.envVars;

      const service = await this.oneboxRef.services.updateService(name, updates);

      // Broadcast service updated
      this.broadcastServiceUpdate(name, 'updated', service);

      return this.jsonResponse({ success: true, data: service });
    } catch (error) {
      logger.error(`Failed to update service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to update service' }, 500);
    }
  }

  private async handleDeleteServiceRequest(name: string): Promise<Response> {
    try {
      await this.oneboxRef.services.removeService(name);

      // Broadcast service deleted
      this.broadcastServiceUpdate(name, 'deleted');

      return this.jsonResponse({ success: true, message: 'Service removed' });
    } catch (error) {
      logger.error(`Failed to delete service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to delete service' }, 500);
    }
  }

  private async handleStartServiceRequest(name: string): Promise<Response> {
    try {
      await this.oneboxRef.services.startService(name);

      // Broadcast service started
      this.broadcastServiceUpdate(name, 'started');

      return this.jsonResponse({ success: true, message: 'Service started' });
    } catch (error) {
      logger.error(`Failed to start service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to start service' }, 500);
    }
  }

  private async handleStopServiceRequest(name: string): Promise<Response> {
    try {
      await this.oneboxRef.services.stopService(name);

      // Broadcast service stopped
      this.broadcastServiceUpdate(name, 'stopped');

      return this.jsonResponse({ success: true, message: 'Service stopped' });
    } catch (error) {
      logger.error(`Failed to stop service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to stop service' }, 500);
    }
  }

  private async handleRestartServiceRequest(name: string): Promise<Response> {
    try {
      await this.oneboxRef.services.restartService(name);

      // Broadcast service updated
      this.broadcastServiceUpdate(name, 'updated');

      return this.jsonResponse({ success: true, message: 'Service restarted' });
    } catch (error) {
      logger.error(`Failed to restart service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to restart service' }, 500);
    }
  }

  private async handleGetLogsRequest(name: string): Promise<Response> {
    try {
      const logs = await this.oneboxRef.services.getServiceLogs(name);
      logger.debug(`handleGetLogsRequest: logs type = ${typeof logs}, constructor = ${logs?.constructor?.name}`);
      logger.debug(`handleGetLogsRequest: logs value = ${String(logs).slice(0, 100)}`);
      return this.jsonResponse({ success: true, data: logs });
    } catch (error) {
      logger.error(`Failed to get logs for service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get logs' }, 500);
    }
  }

  private async handleGetServiceStatsRequest(name: string): Promise<Response> {
    try {
      const service = this.oneboxRef.services.getService(name);
      if (!service) {
        return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
      }

      if (!service.containerID) {
        return this.jsonResponse({ success: false, error: 'Service has no container' }, 400);
      }

      // Get live container stats
      const stats = await this.oneboxRef.docker.getContainerStats(service.containerID);
      if (!stats) {
        return this.jsonResponse({ success: false, error: 'Could not retrieve container stats' }, 500);
      }

      return this.jsonResponse({ success: true, data: stats });
    } catch (error) {
      logger.error(`Failed to get stats for service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get stats' }, 500);
    }
  }

  private async handleGetServiceMetricsRequest(name: string, limit: number): Promise<Response> {
    try {
      const service = this.oneboxRef.services.getService(name);
      if (!service) {
        return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
      }

      if (!service.id) {
        return this.jsonResponse({ success: false, error: 'Service has no ID' }, 400);
      }

      // Get historical metrics from database
      const metrics = this.oneboxRef.database.getMetrics(service.id, limit);

      return this.jsonResponse({ success: true, data: metrics });
    } catch (error) {
      logger.error(`Failed to get metrics for service ${name}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get metrics' }, 500);
    }
  }

  private async handleGetSettingsRequest(): Promise<Response> {
    const settings = this.oneboxRef.database.getAllSettings();
    return this.jsonResponse({ success: true, data: settings });
  }

  private async handleUpdateSettingsRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json();

      if (!body || typeof body !== 'object') {
        return this.jsonResponse(
          { success: false, error: 'Invalid request body' },
          400
        );
      }

      // Handle three formats:
      // 1. Single setting: { key: "settingName", value: "settingValue" }
      // 2. Array format: [{ key: "name1", value: "val1" }, ...]
      // 3. Object format: { settingName1: "value1", settingName2: "value2", ... }

      if (Array.isArray(body)) {
        // Array format from UI
        for (const item of body) {
          if (item.key && typeof item.value === 'string') {
            this.oneboxRef.database.setSetting(item.key, item.value);
            logger.info(`Setting updated: ${item.key} = ${item.value}`);
          }
        }
      } else if (body.key && body.value !== undefined) {
        // Single setting format: { key: "name", value: "val" }
        if (typeof body.value === 'string') {
          this.oneboxRef.database.setSetting(body.key, body.value);
          logger.info(`Setting updated: ${body.key} = ${body.value}`);
        }
      } else {
        // Object format: { name1: "val1", name2: "val2", ... }
        for (const [key, value] of Object.entries(body)) {
          if (typeof value === 'string') {
            this.oneboxRef.database.setSetting(key, value);
            logger.info(`Setting updated: ${key} = ${value}`);
          }
        }
      }

      return this.jsonResponse({
        success: true,
        message: 'Settings updated successfully'
      });
    } catch (error) {
      logger.error(`Failed to update settings: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: 'Failed to update settings' }, 500);
    }
  }

  private async handleObtainCertificateRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json();
      const { domain, includeWildcard } = body;

      if (!domain) {
        return this.jsonResponse(
          { success: false, error: 'Domain is required' },
          400
        );
      }

      await this.oneboxRef.ssl.obtainCertificate(domain, includeWildcard || false);

      return this.jsonResponse({
        success: true,
        message: `Certificate obtained for ${domain}`,
      });
    } catch (error) {
      logger.error(`Failed to obtain certificate: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to obtain certificate' }, 500);
    }
  }

  private async handleListCertificatesRequest(): Promise<Response> {
    try {
      const certificates = this.oneboxRef.ssl.listCertificates();
      return this.jsonResponse({ success: true, data: certificates });
    } catch (error) {
      logger.error(`Failed to list certificates: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to list certificates' }, 500);
    }
  }

  private async handleGetCertificateRequest(domain: string): Promise<Response> {
    try {
      const certificate = this.oneboxRef.ssl.getCertificate(domain);
      if (!certificate) {
        return this.jsonResponse({ success: false, error: 'Certificate not found' }, 404);
      }
      return this.jsonResponse({ success: true, data: certificate });
    } catch (error) {
      logger.error(`Failed to get certificate for ${domain}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to get certificate' }, 500);
    }
  }

  private async handleRenewCertificateRequest(domain: string): Promise<Response> {
    try {
      await this.oneboxRef.ssl.renewCertificate(domain);
      return this.jsonResponse({
        success: true,
        message: `Certificate renewed for ${domain}`,
      });
    } catch (error) {
      logger.error(`Failed to renew certificate for ${domain}: ${getErrorMessage(error)}`);
      return this.jsonResponse({ success: false, error: getErrorMessage(error) || 'Failed to renew certificate' }, 500);
    }
  }

  private async handleGetDomainsRequest(): Promise<Response> {
    try {
      const domains = this.oneboxRef.database.getAllDomains();
      const certManager = this.oneboxRef.certRequirementManager;

      // Build domain views with certificate and service information
      const domainViews = domains.map((domain) => {
        const certificates = this.oneboxRef.database.getCertificatesByDomain(domain.id!);
        const requirements = this.oneboxRef.database.getCertRequirementsByDomain(domain.id!);

        // Count services using this domain
        const allServices = this.oneboxRef.database.getAllServices();
        const serviceCount = allServices.filter((service) => {
          if (!service.domain) return false;
          // Extract base domain from service domain
          const baseDomain = service.domain.split('.').slice(-2).join('.');
          return baseDomain === domain.domain;
        }).length;

        // Determine certificate status
        let certificateStatus: 'valid' | 'expiring-soon' | 'expired' | 'pending' | 'none' =
          'none';
        let daysRemaining: number | null = null;

        const validCerts = certificates.filter((cert) => cert.isValid && cert.expiryDate > Date.now());
        if (validCerts.length > 0) {
          // Find cert with furthest expiry
          const latestCert = validCerts.reduce((latest, cert) =>
            cert.expiryDate > latest.expiryDate ? cert : latest
          );

          daysRemaining = Math.floor((latestCert.expiryDate - Date.now()) / (24 * 60 * 60 * 1000));

          if (daysRemaining <= 30) {
            certificateStatus = 'expiring-soon';
          } else {
            certificateStatus = 'valid';
          }
        } else if (certificates.some((cert) => !cert.isValid)) {
          certificateStatus = 'expired';
        } else if (requirements.some((req) => req.status === 'pending')) {
          certificateStatus = 'pending';
        }

        return {
          domain,
          certificates,
          requirements,
          serviceCount,
          certificateStatus,
          daysRemaining,
        };
      });

      return this.jsonResponse({ success: true, data: domainViews });
    } catch (error) {
      logger.error(`Failed to get domains: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get domains',
      }, 500);
    }
  }

  private async handleSyncDomainsRequest(): Promise<Response> {
    try {
      if (!this.oneboxRef.cloudflareDomainSync) {
        return this.jsonResponse({
          success: false,
          error: 'Cloudflare domain sync not configured',
        }, 400);
      }

      await this.oneboxRef.cloudflareDomainSync.syncZones();

      return this.jsonResponse({
        success: true,
        message: 'Cloudflare zones synced successfully',
      });
    } catch (error) {
      logger.error(`Failed to sync Cloudflare zones: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to sync Cloudflare zones',
      }, 500);
    }
  }

  private async handleGetDomainDetailRequest(domainName: string): Promise<Response> {
    try {
      const domain = this.oneboxRef.database.getDomainByName(domainName);

      if (!domain) {
        return this.jsonResponse({ success: false, error: 'Domain not found' }, 404);
      }

      const certificates = this.oneboxRef.database.getCertificatesByDomain(domain.id!);
      const requirements = this.oneboxRef.database.getCertRequirementsByDomain(domain.id!);

      // Get services using this domain
      const allServices = this.oneboxRef.database.getAllServices();
      const services = allServices.filter((service) => {
        if (!service.domain) return false;
        const baseDomain = service.domain.split('.').slice(-2).join('.');
        return baseDomain === domain.domain;
      });

      // Build detailed view
      const domainDetail = {
        domain,
        certificates: certificates.map((cert) => ({
          ...cert,
          isExpired: cert.expiryDate <= Date.now(),
          daysRemaining: Math.floor((cert.expiryDate - Date.now()) / (24 * 60 * 60 * 1000)),
        })),
        requirements: requirements.map((req) => {
          const service = allServices.find((s) => s.id === req.serviceId);
          return {
            ...req,
            serviceName: service?.name || 'Unknown',
          };
        }),
        services: services.map((s) => ({
          id: s.id,
          name: s.name,
          domain: s.domain,
          status: s.status,
        })),
      };

      return this.jsonResponse({ success: true, data: domainDetail });
    } catch (error) {
      logger.error(`Failed to get domain detail for ${domainName}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get domain detail',
      }, 500);
    }
  }

  private async handleGetDnsRecordsRequest(): Promise<Response> {
    try {
      const records = this.oneboxRef.dns.listDNSRecords();
      return this.jsonResponse({ success: true, data: records });
    } catch (error) {
      logger.error(`Failed to get DNS records: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get DNS records',
      }, 500);
    }
  }

  private async handleCreateDnsRecordRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json();
      const { domain, ip } = body;

      if (!domain) {
        return this.jsonResponse(
          { success: false, error: 'Domain is required' },
          400
        );
      }

      await this.oneboxRef.dns.addDNSRecord(domain, ip);

      return this.jsonResponse({
        success: true,
        message: `DNS record created for ${domain}`,
      });
    } catch (error) {
      logger.error(`Failed to create DNS record: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to create DNS record',
      }, 500);
    }
  }

  private async handleDeleteDnsRecordRequest(domain: string): Promise<Response> {
    try {
      await this.oneboxRef.dns.removeDNSRecord(domain);

      return this.jsonResponse({
        success: true,
        message: `DNS record deleted for ${domain}`,
      });
    } catch (error) {
      logger.error(`Failed to delete DNS record for ${domain}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to delete DNS record',
      }, 500);
    }
  }

  private async handleSyncDnsRecordsRequest(): Promise<Response> {
    try {
      if (!this.oneboxRef.dns.isConfigured()) {
        return this.jsonResponse({
          success: false,
          error: 'DNS manager not configured',
        }, 400);
      }

      await this.oneboxRef.dns.syncFromCloudflare();

      return this.jsonResponse({
        success: true,
        message: 'DNS records synced from Cloudflare',
      });
    } catch (error) {
      logger.error(`Failed to sync DNS records: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to sync DNS records',
      }, 500);
    }
  }

  /**
   * Handle WebSocket upgrade
   */
  private handleWebSocketUpgrade(req: Request): Response {
    const { socket, response } = Deno.upgradeWebSocket(req);

    socket.onopen = () => {
      this.wsClients.add(socket);
      logger.info(`WebSocket client connected (${this.wsClients.size} total)`);

      // Send initial connection message
      socket.send(JSON.stringify({
        type: 'connected',
        message: 'Connected to Onebox server',
        timestamp: Date.now(),
      }));
    };

    socket.onclose = () => {
      this.wsClients.delete(socket);
      logger.info(`WebSocket client disconnected (${this.wsClients.size} remaining)`);
    };

    socket.onerror = (error) => {
      logger.error(`WebSocket error: ${error}`);
      this.wsClients.delete(socket);
    };

    return response;
  }

  /**
   * Handle WebSocket upgrade for log streaming
   */
  private handleLogStreamUpgrade(req: Request, serviceName: string): Response {
    const { socket, response } = Deno.upgradeWebSocket(req);

    socket.onopen = async () => {
      logger.info(`Log stream WebSocket connected for service: ${serviceName}`);

      try {
        // Get the service from database
        const service = this.oneboxRef.database.getServiceByName(serviceName);
        if (!service) {
          socket.send(JSON.stringify({ error: 'Service not found' }));
          socket.close();
          return;
        }

        // Get the container (handle both direct container IDs and service IDs)
        logger.info(`Looking up container for service ${serviceName}, containerID: ${service.containerID}`);
        let container = await this.oneboxRef.docker.getContainerById(service.containerID!);
        logger.info(`Direct lookup result: ${container ? 'found' : 'null'}`);

        // If not found, it might be a service ID - try to get the actual container ID
        if (!container) {
          logger.info('Listing all containers to find matching service...');
          const containers = await this.oneboxRef.docker.listAllContainers();
          logger.info(`Found ${containers.length} containers`);

          const serviceContainer = containers.find((c: any) => {
            const labels = c.Labels || {};
            return labels['com.docker.swarm.service.id'] === service.containerID;
          });

          if (serviceContainer) {
            logger.info(`Found matching container: ${serviceContainer.Id}`);
            container = await this.oneboxRef.docker.getContainerById(serviceContainer.Id);
            logger.info(`Second lookup result: ${container ? 'found' : 'null'}`);
          } else {
            logger.error(`No container found with service label matching ${service.containerID}`);
          }
        }

        if (!container) {
          logger.error(`Container not found for service ${serviceName}, containerID: ${service.containerID}`);
          socket.send(JSON.stringify({ error: 'Container not found' }));
          socket.close();
          return;
        }

        // Start streaming logs
        const logStream = await container.streamLogs({
          stdout: true,
          stderr: true,
          timestamps: true,
          tail: 100, // Start with last 100 lines
        });

        // Send initial connection message
        socket.send(JSON.stringify({
          type: 'connected',
          serviceName: service.name,
        }));

        // Demultiplex and pipe log data to WebSocket
        // Docker streams use 8-byte headers: [STREAM_TYPE, 0, 0, 0, SIZE_BYTE1, SIZE_BYTE2, SIZE_BYTE3, SIZE_BYTE4]
        let buffer = new Uint8Array(0);

        logStream.on('data', (chunk: Uint8Array) => {
          if (socket.readyState !== WebSocket.OPEN) return;

          // Append new data to buffer
          const newBuffer = new Uint8Array(buffer.length + chunk.length);
          newBuffer.set(buffer);
          newBuffer.set(chunk, buffer.length);
          buffer = newBuffer;

          // Process complete frames
          while (buffer.length >= 8) {
            // Read frame size from header (bytes 4-7, big-endian)
            const frameSize = (buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7];

            // Check if we have the complete frame
            if (buffer.length < 8 + frameSize) {
              break; // Wait for more data
            }

            // Extract the frame data (skip 8-byte header)
            const frameData = buffer.slice(8, 8 + frameSize);

            // Send the clean log line
            socket.send(new TextDecoder().decode(frameData));

            // Remove processed frame from buffer
            buffer = buffer.slice(8 + frameSize);
          }
        });

        logStream.on('error', (error: Error) => {
          logger.error(`Log stream error for ${serviceName}: ${getErrorMessage(error)}`);
          if (socket.readyState === WebSocket.OPEN) {
            socket.send(JSON.stringify({ error: getErrorMessage(error) }));
          }
        });

        logStream.on('end', () => {
          logger.info(`Log stream ended for ${serviceName}`);
          socket.close();
        });

        // Clean up on close
        socket.onclose = () => {
          logger.info(`Log stream WebSocket closed for ${serviceName}`);
          logStream.destroy();
        };

      } catch (error) {
        logger.error(`Failed to start log stream for ${serviceName}: ${getErrorMessage(error)}`);
        if (socket.readyState === WebSocket.OPEN) {
          socket.send(JSON.stringify({ error: getErrorMessage(error) }));
          socket.close();
        }
      }
    };

    socket.onerror = (error) => {
      logger.error(`Log stream WebSocket error: ${error}`);
    };

    return response;
  }

  /**
   * Handle WebSocket upgrade for platform service log streaming
   */
  private handlePlatformLogStreamUpgrade(req: Request, platformType: string): Response {
    const { socket, response } = Deno.upgradeWebSocket(req);

    socket.onopen = async () => {
      logger.info(`Platform log stream WebSocket connected for: ${platformType}`);

      try {
        // Get the platform service from database
        const platformService = this.oneboxRef.database.getPlatformServiceByType(platformType as any);
        if (!platformService) {
          socket.send(JSON.stringify({ error: 'Platform service not found' }));
          socket.close();
          return;
        }

        if (!platformService.containerId) {
          socket.send(JSON.stringify({ error: 'Platform service has no container' }));
          socket.close();
          return;
        }

        // Get the container
        logger.info(`Looking up container for platform service ${platformType}, containerID: ${platformService.containerId}`);
        const container = await this.oneboxRef.docker.getContainerById(platformService.containerId);

        if (!container) {
          logger.error(`Container not found for platform service ${platformType}, containerID: ${platformService.containerId}`);
          socket.send(JSON.stringify({ error: 'Container not found' }));
          socket.close();
          return;
        }

        // Start streaming logs
        const logStream = await container.streamLogs({
          stdout: true,
          stderr: true,
          timestamps: true,
          tail: 100, // Start with last 100 lines
        });

        // Send initial connection message
        socket.send(JSON.stringify({
          type: 'connected',
          serviceName: platformType,
        }));

        // Demultiplex and pipe log data to WebSocket
        // Docker streams use 8-byte headers: [STREAM_TYPE, 0, 0, 0, SIZE_BYTE1, SIZE_BYTE2, SIZE_BYTE3, SIZE_BYTE4]
        let buffer = new Uint8Array(0);

        logStream.on('data', (chunk: Uint8Array) => {
          if (socket.readyState !== WebSocket.OPEN) return;

          // Append new data to buffer
          const newBuffer = new Uint8Array(buffer.length + chunk.length);
          newBuffer.set(buffer);
          newBuffer.set(chunk, buffer.length);
          buffer = newBuffer;

          // Process complete frames
          while (buffer.length >= 8) {
            // Read frame size from header (bytes 4-7, big-endian)
            const frameSize = (buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7];

            // Check if we have the complete frame
            if (buffer.length < 8 + frameSize) {
              break; // Wait for more data
            }

            // Extract the frame data (skip 8-byte header)
            const frameData = buffer.slice(8, 8 + frameSize);

            // Send the clean log line
            socket.send(new TextDecoder().decode(frameData));

            // Remove processed frame from buffer
            buffer = buffer.slice(8 + frameSize);
          }
        });

        logStream.on('error', (error: Error) => {
          logger.error(`Platform log stream error for ${platformType}: ${getErrorMessage(error)}`);
          if (socket.readyState === WebSocket.OPEN) {
            socket.send(JSON.stringify({ error: getErrorMessage(error) }));
          }
        });

        logStream.on('end', () => {
          logger.info(`Platform log stream ended for ${platformType}`);
          socket.close();
        });

        // Clean up on close
        socket.onclose = () => {
          logger.info(`Platform log stream WebSocket closed for ${platformType}`);
          logStream.destroy();
        };

      } catch (error) {
        logger.error(`Failed to start platform log stream for ${platformType}: ${getErrorMessage(error)}`);
        if (socket.readyState === WebSocket.OPEN) {
          socket.send(JSON.stringify({ error: getErrorMessage(error) }));
          socket.close();
        }
      }
    };

    socket.onerror = (error) => {
      logger.error(`Platform log stream WebSocket error: ${error}`);
    };

    return response;
  }

  /**
   * Handle WebSocket upgrade for network access log streaming
   */
  private handleNetworkLogStreamUpgrade(req: Request, url: URL): Response {
    const { socket, response } = Deno.upgradeWebSocket(req);

    // Extract filter from query params
    const filterDomain = url.searchParams.get('domain');

    // Generate unique client ID
    const clientId = crypto.randomUUID();

    socket.onopen = () => {
      logger.info(`Network log stream WebSocket connected (client: ${clientId})`);

      // Register with CaddyLogReceiver
      const filter = filterDomain ? { domain: filterDomain } : {};
      this.oneboxRef.caddyLogReceiver.addClient(clientId, socket, filter);

      // Send initial connection message
      socket.send(JSON.stringify({
        type: 'connected',
        clientId,
        filter,
      }));
    };

    socket.onmessage = (event) => {
      try {
        const message = JSON.parse(event.data);

        // Handle filter updates from client
        if (message.type === 'set_filter') {
          const newFilter = {
            domain: message.domain || undefined,
            sampleRate: message.sampleRate || undefined,
          };
          this.oneboxRef.caddyLogReceiver.updateClientFilter(clientId, newFilter);

          socket.send(JSON.stringify({
            type: 'filter_updated',
            filter: newFilter,
          }));
        }
      } catch (error) {
        logger.debug(`Network log stream message parse error: ${getErrorMessage(error)}`);
      }
    };

    socket.onclose = () => {
      logger.info(`Network log stream WebSocket closed (client: ${clientId})`);
      this.oneboxRef.caddyLogReceiver.removeClient(clientId);
    };

    socket.onerror = (error) => {
      logger.error(`Network log stream WebSocket error: ${error}`);
      this.oneboxRef.caddyLogReceiver.removeClient(clientId);
    };

    return response;
  }

  // ============ Network Endpoints ============

  /**
   * Get all traffic targets (services, registry, platform services)
   */
  private async handleGetNetworkTargetsRequest(): Promise<Response> {
    try {
      const targets: Array<{
        type: 'service' | 'registry' | 'platform';
        name: string;
        domain: string | null;
        targetHost: string;
        targetPort: number;
        status: string;
      }> = [];

      // Add services
      const services = this.oneboxRef.services.listServices();
      for (const service of services) {
        targets.push({
          type: 'service',
          name: service.name,
          domain: service.domain || null,
          targetHost: service.containerIP || 'unknown',
          targetPort: service.port || 80,
          status: service.status,
        });
      }

      // Add registry if running
      const registryStatus = this.oneboxRef.registry.getStatus();
      if (registryStatus.running) {
        targets.push({
          type: 'registry',
          name: 'onebox-registry',
          domain: null, // Registry is internal
          targetHost: 'localhost',
          targetPort: registryStatus.port,
          status: 'running',
        });
      }

      // Add platform services
      const platformServices = this.oneboxRef.platformServices.getAllPlatformServices();
      for (const ps of platformServices) {
        // Get provider info for display name
        const provider = this.oneboxRef.platformServices.getProvider(ps.type);
        targets.push({
          type: 'platform',
          name: provider?.displayName || ps.type,
          domain: null, // Platform services are internal
          targetHost: 'localhost',
          targetPort: this.getPlatformServicePort(ps.type),
          status: ps.status,
        });
      }

      return this.jsonResponse({ success: true, data: targets });
    } catch (error) {
      logger.error(`Failed to get network targets: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get network targets',
      }, 500);
    }
  }

  /**
   * Get default port for a platform service type
   */
  private getPlatformServicePort(type: TPlatformServiceType): number {
    const ports: Record<TPlatformServiceType, number> = {
      mongodb: 27017,
      minio: 9000,
      redis: 6379,
      postgresql: 5432,
      rabbitmq: 5672,
      caddy: 80,
      clickhouse: 8123,
    };
    return ports[type] || 0;
  }

  /**
   * Get Caddy/network stats
   */
  private async handleGetNetworkStatsRequest(): Promise<Response> {
    try {
      const proxyStatus = this.oneboxRef.reverseProxy.getStatus();
      const logReceiverStats = this.oneboxRef.caddyLogReceiver.getStats();

      return this.jsonResponse({
        success: true,
        data: {
          proxy: {
            running: proxyStatus.running,
            httpPort: proxyStatus.httpPort,
            httpsPort: proxyStatus.httpsPort,
            routes: proxyStatus.routes,
            certificates: proxyStatus.certificates,
          },
          logReceiver: {
            running: logReceiverStats.running,
            port: logReceiverStats.port,
            clients: logReceiverStats.clients,
            connections: logReceiverStats.connections,
            sampleRate: logReceiverStats.sampleRate,
            recentLogsCount: logReceiverStats.recentLogsCount,
          },
        },
      });
    } catch (error) {
      logger.error(`Failed to get network stats: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get network stats',
      }, 500);
    }
  }

  /**
   * Get traffic stats from Caddy access logs
   */
  private async handleGetTrafficStatsRequest(url: URL): Promise<Response> {
    try {
      // Get minutes parameter (default: 60)
      const minutesParam = url.searchParams.get('minutes');
      const minutes = minutesParam ? parseInt(minutesParam, 10) : 60;

      if (isNaN(minutes) || minutes < 1 || minutes > 60) {
        return this.jsonResponse({
          success: false,
          error: 'Invalid minutes parameter. Must be between 1 and 60.',
        }, 400);
      }

      const trafficStats = this.oneboxRef.caddyLogReceiver.getTrafficStats(minutes);

      return this.jsonResponse({
        success: true,
        data: trafficStats,
      });
    } catch (error) {
      logger.error(`Failed to get traffic stats: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get traffic stats',
      }, 500);
    }
  }

  /**
   * Broadcast message to all connected WebSocket clients
   */
  broadcast(message: Record<string, any>): void {
    const data = JSON.stringify(message);
    let successCount = 0;
    let failCount = 0;

    for (const client of this.wsClients) {
      try {
        if (client.readyState === WebSocket.OPEN) {
          client.send(data);
          successCount++;
        } else {
          this.wsClients.delete(client);
          failCount++;
        }
      } catch (error) {
        logger.error(`Failed to send to WebSocket client: ${getErrorMessage(error)}`);
        this.wsClients.delete(client);
        failCount++;
      }
    }

    if (successCount > 0) {
      logger.debug(`Broadcast to ${successCount} clients (${failCount} failed)`);
    }
  }

  /**
   * Broadcast service update
   */
  broadcastServiceUpdate(serviceName: string, action: 'created' | 'updated' | 'deleted' | 'started' | 'stopped', data?: any): void {
    this.broadcast({
      type: 'service_update',
      action,
      serviceName,
      data,
      timestamp: Date.now(),
    });
  }

  /**
   * Broadcast service status update
   */
  broadcastServiceStatus(serviceName: string, status: string): void {
    this.broadcast({
      type: 'service_status',
      serviceName,
      status,
      timestamp: Date.now(),
    });
  }

  /**
   * Broadcast system status update
   */
  broadcastSystemStatus(status: any): void {
    this.broadcast({
      type: 'system_status',
      data: status,
      timestamp: Date.now(),
    });
  }

  /**
   * Broadcast stats update for a service
   */
  broadcastStatsUpdate(serviceName: string, stats: IContainerStats): void {
    this.broadcast({
      type: 'stats_update',
      serviceName,
      stats,
      timestamp: Date.now(),
    });
  }

  // ============ Platform Services Endpoints ============

  private async handleListPlatformServicesRequest(): Promise<Response> {
    try {
      const platformServices = this.oneboxRef.platformServices.getAllPlatformServices();
      const providers = this.oneboxRef.platformServices.getAllProviders();

      // Build response with provider info
      const result = providers.map((provider) => {
        const service = platformServices.find((s) => s.type === provider.type);
        // Check if provider has isCore property (like CaddyProvider)
        const isCore = 'isCore' in provider && (provider as any).isCore === true;

        // For Caddy, check actual runtime status since it starts without a DB record
        let status = service?.status || 'not-deployed';
        if (provider.type === 'caddy') {
          const proxyStatus = this.oneboxRef.reverseProxy.getStatus();
          status = proxyStatus.http.running ? 'running' : 'stopped';
        }

        return {
          type: provider.type,
          displayName: provider.displayName,
          resourceTypes: provider.resourceTypes,
          status,
          containerId: service?.containerId,
          isCore,
          createdAt: service?.createdAt,
          updatedAt: service?.updatedAt,
        };
      });

      return this.jsonResponse({ success: true, data: result });
    } catch (error) {
      logger.error(`Failed to list platform services: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to list platform services',
      }, 500);
    }
  }

  private async handleGetPlatformServiceRequest(type: TPlatformServiceType): Promise<Response> {
    try {
      const provider = this.oneboxRef.platformServices.getProvider(type);
      if (!provider) {
        return this.jsonResponse({
          success: false,
          error: `Unknown platform service type: ${type}`,
        }, 404);
      }

      const service = this.oneboxRef.database.getPlatformServiceByType(type);

      // Get resource count
      const allResources = service?.id
        ? this.oneboxRef.database.getPlatformResourcesByPlatformService(service.id)
        : [];

      // For Caddy, check actual runtime status since it starts without a DB record
      let status = service?.status || 'not-deployed';
      if (type === 'caddy') {
        const proxyStatus = this.oneboxRef.reverseProxy.getStatus();
        status = proxyStatus.http.running ? 'running' : 'stopped';
      }

      return this.jsonResponse({
        success: true,
        data: {
          type: provider.type,
          displayName: provider.displayName,
          resourceTypes: provider.resourceTypes,
          status,
          containerId: service?.containerId,
          config: provider.getDefaultConfig(),
          resourceCount: allResources.length,
          createdAt: service?.createdAt,
          updatedAt: service?.updatedAt,
        },
      });
    } catch (error) {
      logger.error(`Failed to get platform service ${type}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get platform service',
      }, 500);
    }
  }

  private async handleStartPlatformServiceRequest(type: TPlatformServiceType): Promise<Response> {
    try {
      const provider = this.oneboxRef.platformServices.getProvider(type);
      if (!provider) {
        return this.jsonResponse({
          success: false,
          error: `Unknown platform service type: ${type}`,
        }, 404);
      }

      logger.info(`Starting platform service: ${type}`);
      const service = await this.oneboxRef.platformServices.ensureRunning(type);

      return this.jsonResponse({
        success: true,
        message: `Platform service ${provider.displayName} started`,
        data: {
          type: service.type,
          status: service.status,
          containerId: service.containerId,
        },
      });
    } catch (error) {
      logger.error(`Failed to start platform service ${type}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to start platform service',
      }, 500);
    }
  }

  private async handleStopPlatformServiceRequest(type: TPlatformServiceType): Promise<Response> {
    try {
      const provider = this.oneboxRef.platformServices.getProvider(type);
      if (!provider) {
        return this.jsonResponse({
          success: false,
          error: `Unknown platform service type: ${type}`,
        }, 404);
      }

      // Check if this is a core service that cannot be stopped
      const isCore = 'isCore' in provider && (provider as any).isCore === true;
      if (isCore) {
        return this.jsonResponse({
          success: false,
          error: `${provider.displayName} is a core service and cannot be stopped`,
        }, 400);
      }

      logger.info(`Stopping platform service: ${type}`);
      await this.oneboxRef.platformServices.stopPlatformService(type);

      return this.jsonResponse({
        success: true,
        message: `Platform service ${provider.displayName} stopped`,
      });
    } catch (error) {
      logger.error(`Failed to stop platform service ${type}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to stop platform service',
      }, 500);
    }
  }

  private async handleGetPlatformServiceStatsRequest(type: TPlatformServiceType): Promise<Response> {
    try {
      const provider = this.oneboxRef.platformServices.getProvider(type);
      if (!provider) {
        return this.jsonResponse({
          success: false,
          error: `Unknown platform service type: ${type}`,
        }, 404);
      }

      // For Caddy, return proxy stats instead of container stats
      if (type === 'caddy') {
        const proxyStatus = this.oneboxRef.reverseProxy.getStatus();
        return this.jsonResponse({
          success: true,
          data: {
            type: 'caddy',
            running: proxyStatus.http.running,
            httpPort: proxyStatus.http.port,
            httpsPort: proxyStatus.https.port,
            routes: proxyStatus.routes,
            certificates: proxyStatus.https.certificates,
          },
        });
      }

      const service = this.oneboxRef.database.getPlatformServiceByType(type);
      if (!service || !service.containerId) {
        return this.jsonResponse({ success: false, error: 'Platform service has no container' }, 400);
      }

      const stats = await this.oneboxRef.docker.getContainerStats(service.containerId);
      if (!stats) {
        return this.jsonResponse({ success: false, error: 'Could not retrieve container stats' }, 500);
      }

      return this.jsonResponse({ success: true, data: stats });
    } catch (error) {
      logger.error(`Failed to get stats for platform service ${type}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get platform service stats',
      }, 500);
    }
  }

  private async handleGetServicePlatformResourcesRequest(serviceName: string): Promise<Response> {
    try {
      const service = this.oneboxRef.services.getService(serviceName);
      if (!service) {
        return this.jsonResponse({
          success: false,
          error: 'Service not found',
        }, 404);
      }

      const resources = await this.oneboxRef.services.getServicePlatformResources(serviceName);

      // Format resources for API response (mask sensitive credentials)
      const formattedResources = resources.map((r) => ({
        id: r.resource.id,
        resourceType: r.resource.resourceType,
        resourceName: r.resource.resourceName,
        platformService: {
          type: r.platformService.type,
          name: r.platformService.name,
          status: r.platformService.status,
        },
        // Include env var mappings (show keys, not values)
        envVars: Object.keys(r.credentials).reduce((acc, key) => {
          // Mask sensitive values
          const value = r.credentials[key];
          if (key.toLowerCase().includes('password') || key.toLowerCase().includes('secret')) {
            acc[key] = '********';
          } else {
            acc[key] = value;
          }
          return acc;
        }, {} as Record<string, string>),
        createdAt: r.resource.createdAt,
      }));

      return this.jsonResponse({
        success: true,
        data: formattedResources,
      });
    } catch (error) {
      logger.error(`Failed to get platform resources for service ${serviceName}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get platform resources',
      }, 500);
    }
  }

  // ============ Registry Endpoints ============

  /**
   * Handle Docker registry token request (OCI token authentication)
   * Docker calls this endpoint to get a bearer token for registry operations
   *
   * Query params:
   * - service: The registry service name
   * - scope: Permission scope (e.g., "repository:hello-world:push,pull")
   * - account: Optional account name (for basic auth)
   */
  private async handleRegistryTokenRequest(req: Request, url: URL): Promise<Response> {
    try {
      const service = url.searchParams.get('service') || 'onebox-registry';
      const scope = url.searchParams.get('scope');
      const account = url.searchParams.get('account') || 'anonymous';

      logger.info(`Registry token request: service=${service}, scope=${scope}, account=${account}`);

      // Parse scope to extract repository and actions
      // Format: repository:name:action1,action2 (e.g., "repository:hello-world:push,pull")
      let scopes: string[] = [];

      if (scope) {
        const scopeParts = scope.split(':');
        if (scopeParts.length >= 3 && scopeParts[0] === 'repository') {
          const repository = scopeParts[1];
          // For now, grant both push and pull for any repository request
          // This allows anonymous push to the local registry
          // TODO: Add authentication and authorization to restrict access
          scopes = [
            `oci:repository:${repository}:push`,
            `oci:repository:${repository}:pull`,
          ];
        }
      }

      // If no scope specified, grant basic access
      if (scopes.length === 0) {
        scopes = ['oci:repository:*:pull'];
      }

      logger.info(`Creating OCI token with scopes: ${scopes.join(', ')}`);

      // Use the registry's auth manager to create a token
      // smartregistry v2.0.0 returns proper JWT format (header.payload.signature)
      const authManager = this.oneboxRef.registry.getAuthManager();
      const token = await authManager.createOciToken(account, scopes, 3600);

      logger.info(`Token created (JWT length: ${token.length})`);

      // Return in Docker-expected format
      return new Response(JSON.stringify({
        token,
        access_token: token,
        expires_in: 3600,
        issued_at: new Date().toISOString(),
      }), {
        status: 200,
        headers: {
          'Content-Type': 'application/json',
        },
      });
    } catch (error) {
      logger.error(`Registry token error: ${getErrorMessage(error)}`);
      return new Response(JSON.stringify({
        error: 'token_error',
        error_description: getErrorMessage(error),
      }), {
        status: 500,
        headers: { 'Content-Type': 'application/json' },
      });
    }
  }

  private async handleGetRegistryTagsRequest(serviceName: string): Promise<Response> {
    try {
      const tags = await this.oneboxRef.registry.getImageTags(serviceName);
      return this.jsonResponse({ success: true, data: tags });
    } catch (error) {
      logger.error(`Failed to get registry tags for ${serviceName}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get registry tags',
      }, 500);
    }
  }

  // ============ Registry Token Management Endpoints ============

  private async handleListRegistryTokensRequest(req: Request): Promise<Response> {
    try {
      const tokens = this.oneboxRef.database.getAllRegistryTokens();

      // Convert to view format (mask token hash, add computed fields)
      const tokenViews: IRegistryTokenView[] = tokens.map(token => {
        const now = Date.now();
        const isExpired = token.expiresAt !== null && token.expiresAt < now;

        // Generate scope display string
        let scopeDisplay: string;
        if (token.scope === 'all') {
          scopeDisplay = 'All services';
        } else if (Array.isArray(token.scope)) {
          scopeDisplay = token.scope.length === 1
            ? token.scope[0]
            : `${token.scope.length} services`;
        } else {
          scopeDisplay = 'Unknown';
        }

        return {
          id: token.id!,
          name: token.name,
          type: token.type,
          scope: token.scope,
          scopeDisplay,
          expiresAt: token.expiresAt,
          createdAt: token.createdAt,
          lastUsedAt: token.lastUsedAt,
          createdBy: token.createdBy,
          isExpired,
        };
      });

      return this.jsonResponse({ success: true, data: tokenViews });
    } catch (error) {
      logger.error(`Failed to list registry tokens: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to list registry tokens',
      }, 500);
    }
  }

  private async handleCreateRegistryTokenRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json() as ICreateRegistryTokenRequest;

      // Validate request
      if (!body.name || !body.type || !body.scope || !body.expiresIn) {
        return this.jsonResponse({
          success: false,
          error: 'Missing required fields: name, type, scope, expiresIn',
        }, 400);
      }

      if (body.type !== 'global' && body.type !== 'ci') {
        return this.jsonResponse({
          success: false,
          error: 'Invalid token type. Must be "global" or "ci"',
        }, 400);
      }

      // Validate scope
      if (body.scope !== 'all' && !Array.isArray(body.scope)) {
        return this.jsonResponse({
          success: false,
          error: 'Scope must be "all" or an array of service names',
        }, 400);
      }

      // If scope is array of services, validate they exist
      if (Array.isArray(body.scope)) {
        for (const serviceName of body.scope) {
          const service = this.oneboxRef.database.getServiceByName(serviceName);
          if (!service) {
            return this.jsonResponse({
              success: false,
              error: `Service not found: ${serviceName}`,
            }, 400);
          }
        }
      }

      // Calculate expiration timestamp
      const now = Date.now();
      let expiresAt: number | null = null;
      if (body.expiresIn !== 'never') {
        const daysMap: Record<string, number> = {
          '30d': 30,
          '90d': 90,
          '365d': 365,
        };
        const days = daysMap[body.expiresIn];
        if (days) {
          expiresAt = now + (days * 24 * 60 * 60 * 1000);
        }
      }

      // Generate token (random 32 bytes as hex)
      const plainToken = crypto.randomUUID() + crypto.randomUUID();

      // Hash the token for storage (using simple hash for now)
      const encoder = new TextEncoder();
      const data = encoder.encode(plainToken);
      const hashBuffer = await crypto.subtle.digest('SHA-256', data);
      const hashArray = Array.from(new Uint8Array(hashBuffer));
      const tokenHash = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');

      // Get username from auth token
      const authHeader = req.headers.get('Authorization');
      let createdBy = 'system';
      if (authHeader && authHeader.startsWith('Bearer ')) {
        try {
          const decoded = atob(authHeader.slice(7));
          createdBy = decoded.split(':')[0];
        } catch {
          // Keep default
        }
      }

      // Create token in database
      const token = this.oneboxRef.database.createRegistryToken({
        name: body.name,
        tokenHash,
        type: body.type,
        scope: body.scope,
        expiresAt,
        createdAt: now,
        lastUsedAt: null,
        createdBy,
      });

      // Build view response
      let scopeDisplay: string;
      if (token.scope === 'all') {
        scopeDisplay = 'All services';
      } else if (Array.isArray(token.scope)) {
        scopeDisplay = token.scope.length === 1
          ? token.scope[0]
          : `${token.scope.length} services`;
      } else {
        scopeDisplay = 'Unknown';
      }

      const tokenView: IRegistryTokenView = {
        id: token.id!,
        name: token.name,
        type: token.type,
        scope: token.scope,
        scopeDisplay,
        expiresAt: token.expiresAt,
        createdAt: token.createdAt,
        lastUsedAt: token.lastUsedAt,
        createdBy: token.createdBy,
        isExpired: false,
      };

      return this.jsonResponse({
        success: true,
        data: {
          token: tokenView,
          plainToken, // Only returned once at creation
        },
      });
    } catch (error) {
      logger.error(`Failed to create registry token: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to create registry token',
      }, 500);
    }
  }

  private async handleDeleteRegistryTokenRequest(tokenId: number): Promise<Response> {
    try {
      // Check if token exists
      const token = this.oneboxRef.database.getRegistryTokenById(tokenId);
      if (!token) {
        return this.jsonResponse({
          success: false,
          error: 'Token not found',
        }, 404);
      }

      // Delete the token
      this.oneboxRef.database.deleteRegistryToken(tokenId);

      return this.jsonResponse({
        success: true,
        message: 'Token deleted successfully',
      });
    } catch (error) {
      logger.error(`Failed to delete registry token ${tokenId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to delete registry token',
      }, 500);
    }
  }

  // ============ Backup Endpoints ============

  /**
   * List all backups
   */
  private async handleListBackupsRequest(): Promise<Response> {
    try {
      const backups = this.oneboxRef.backupManager.listBackups();
      return this.jsonResponse({ success: true, data: backups });
    } catch (error) {
      logger.error(`Failed to list backups: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to list backups',
      }, 500);
    }
  }

  /**
   * List backups for a specific service
   */
  private async handleListServiceBackupsRequest(serviceName: string): Promise<Response> {
    try {
      const service = this.oneboxRef.services.getService(serviceName);
      if (!service) {
        return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
      }

      const backups = this.oneboxRef.backupManager.listBackups(serviceName);
      return this.jsonResponse({ success: true, data: backups });
    } catch (error) {
      logger.error(`Failed to list backups for service ${serviceName}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to list backups',
      }, 500);
    }
  }

  /**
   * Create a backup for a service
   */
  private async handleCreateBackupRequest(serviceName: string): Promise<Response> {
    try {
      const service = this.oneboxRef.services.getService(serviceName);
      if (!service) {
        return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
      }

      const result = await this.oneboxRef.backupManager.createBackup(serviceName);

      return this.jsonResponse({
        success: true,
        message: `Backup created for service ${serviceName}`,
        data: result.backup,
      });
    } catch (error) {
      logger.error(`Failed to create backup for service ${serviceName}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to create backup',
      }, 500);
    }
  }

  /**
   * Get a specific backup by ID
   */
  private async handleGetBackupRequest(backupId: number): Promise<Response> {
    try {
      const backup = this.oneboxRef.database.getBackupById(backupId);
      if (!backup) {
        return this.jsonResponse({ success: false, error: 'Backup not found' }, 404);
      }

      return this.jsonResponse({ success: true, data: backup });
    } catch (error) {
      logger.error(`Failed to get backup ${backupId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get backup',
      }, 500);
    }
  }

  /**
   * Download a backup file
   */
  private async handleDownloadBackupRequest(backupId: number): Promise<Response> {
    try {
      const filePath = this.oneboxRef.backupManager.getBackupFilePath(backupId);
      if (!filePath) {
        return this.jsonResponse({ success: false, error: 'Backup not found' }, 404);
      }

      // Check if file exists
      try {
        await Deno.stat(filePath);
      } catch {
        return this.jsonResponse({ success: false, error: 'Backup file not found on disk' }, 404);
      }

      // Read file and return as download
      const backup = this.oneboxRef.database.getBackupById(backupId);
      const file = await Deno.readFile(filePath);

      return new Response(file, {
        status: 200,
        headers: {
          'Content-Type': 'application/octet-stream',
          'Content-Disposition': `attachment; filename="${backup?.filename || 'backup.tar.enc'}"`,
          'Content-Length': String(file.length),
        },
      });
    } catch (error) {
      logger.error(`Failed to download backup ${backupId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to download backup',
      }, 500);
    }
  }

  /**
   * Delete a backup
   */
  private async handleDeleteBackupRequest(backupId: number): Promise<Response> {
    try {
      const backup = this.oneboxRef.database.getBackupById(backupId);
      if (!backup) {
        return this.jsonResponse({ success: false, error: 'Backup not found' }, 404);
      }

      await this.oneboxRef.backupManager.deleteBackup(backupId);

      return this.jsonResponse({
        success: true,
        message: 'Backup deleted successfully',
      });
    } catch (error) {
      logger.error(`Failed to delete backup ${backupId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to delete backup',
      }, 500);
    }
  }

  /**
   * Restore a backup
   */
  private async handleRestoreBackupRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json();
      const { backupId, mode, newServiceName, overwriteExisting, skipPlatformData } = body;

      if (!backupId) {
        return this.jsonResponse({
          success: false,
          error: 'Backup ID is required',
        }, 400);
      }

      if (!mode || !['restore', 'import', 'clone'].includes(mode)) {
        return this.jsonResponse({
          success: false,
          error: 'Valid mode required: restore, import, or clone',
        }, 400);
      }

      // Get backup file path
      const filePath = this.oneboxRef.backupManager.getBackupFilePath(backupId);
      if (!filePath) {
        return this.jsonResponse({ success: false, error: 'Backup not found' }, 404);
      }

      // Validate mode-specific requirements
      if ((mode === 'import' || mode === 'clone') && !newServiceName) {
        return this.jsonResponse({
          success: false,
          error: `New service name required for '${mode}' mode`,
        }, 400);
      }

      const result = await this.oneboxRef.backupManager.restoreBackup(filePath, {
        mode,
        newServiceName,
        overwriteExisting: overwriteExisting === true,
        skipPlatformData: skipPlatformData === true,
      });

      return this.jsonResponse({
        success: true,
        message: `Backup restored successfully as service '${result.service.name}'`,
        data: {
          service: result.service,
          platformResourcesRestored: result.platformResourcesRestored,
          warnings: result.warnings,
        },
      });
    } catch (error) {
      logger.error(`Failed to restore backup: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to restore backup',
      }, 500);
    }
  }

  /**
   * Import a backup from file upload or URL
   */
  private async handleImportBackupRequest(req: Request): Promise<Response> {
    try {
      const contentType = req.headers.get('content-type') || '';
      let filePath: string | null = null;
      let newServiceName: string | undefined;
      let tempFile = false;

      if (contentType.includes('multipart/form-data')) {
        // Handle file upload
        const formData = await req.formData();
        const file = formData.get('file');
        newServiceName = formData.get('newServiceName')?.toString() || undefined;

        if (!file || !(file instanceof File)) {
          return this.jsonResponse({
            success: false,
            error: 'No file provided',
          }, 400);
        }

        // Validate file extension
        if (!file.name.endsWith('.tar.enc')) {
          return this.jsonResponse({
            success: false,
            error: 'Invalid file format. Expected .tar.enc file',
          }, 400);
        }

        // Save to temp location
        const tempDir = './.nogit/temp-imports';
        await Deno.mkdir(tempDir, { recursive: true });
        filePath = `${tempDir}/${Date.now()}-${file.name}`;
        tempFile = true;

        const arrayBuffer = await file.arrayBuffer();
        await Deno.writeFile(filePath, new Uint8Array(arrayBuffer));

        logger.info(`Saved uploaded backup to ${filePath}`);
      } else {
        // Handle JSON body with URL
        const body = await req.json();
        const { url, newServiceName: serviceName } = body;
        newServiceName = serviceName;

        if (!url) {
          return this.jsonResponse({
            success: false,
            error: 'URL is required when not uploading a file',
          }, 400);
        }

        // Download from URL
        const tempDir = './.nogit/temp-imports';
        await Deno.mkdir(tempDir, { recursive: true });

        const urlFilename = url.split('/').pop() || 'backup.tar.enc';
        filePath = `${tempDir}/${Date.now()}-${urlFilename}`;
        tempFile = true;

        logger.info(`Downloading backup from ${url}...`);
        const response = await fetch(url);
        if (!response.ok) {
          return this.jsonResponse({
            success: false,
            error: `Failed to download from URL: ${response.statusText}`,
          }, 400);
        }

        const arrayBuffer = await response.arrayBuffer();
        await Deno.writeFile(filePath, new Uint8Array(arrayBuffer));

        logger.info(`Downloaded backup to ${filePath}`);
      }

      // Import using restoreBackup with mode='import'
      const result = await this.oneboxRef.backupManager.restoreBackup(filePath, {
        mode: 'import',
        newServiceName,
        overwriteExisting: false,
        skipPlatformData: false,
      });

      // Clean up temp file
      if (tempFile && filePath) {
        try {
          await Deno.remove(filePath);
        } catch {
          // Ignore cleanup errors
        }
      }

      return this.jsonResponse({
        success: true,
        message: `Backup imported successfully as service '${result.service.name}'`,
        data: {
          service: result.service,
          platformResourcesRestored: result.platformResourcesRestored,
          warnings: result.warnings,
        },
      });
    } catch (error) {
      logger.error(`Failed to import backup: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to import backup',
      }, 500);
    }
  }

  /**
   * Set backup encryption password
   */
  private async handleSetBackupPasswordRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json();
      const { password } = body;

      if (!password || typeof password !== 'string') {
        return this.jsonResponse({
          success: false,
          error: 'Password is required',
        }, 400);
      }

      if (password.length < 8) {
        return this.jsonResponse({
          success: false,
          error: 'Password must be at least 8 characters',
        }, 400);
      }

      // Store password in settings
      this.oneboxRef.database.setSetting('backup_encryption_password', password);

      return this.jsonResponse({
        success: true,
        message: 'Backup password set successfully',
      });
    } catch (error) {
      logger.error(`Failed to set backup password: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to set backup password',
      }, 500);
    }
  }

  /**
   * Check if backup password is configured
   */
  private async handleCheckBackupPasswordRequest(): Promise<Response> {
    try {
      const password = this.oneboxRef.database.getSetting('backup_encryption_password');
      const isConfigured = password !== null && password.length > 0;

      return this.jsonResponse({
        success: true,
        data: {
          isConfigured,
        },
      });
    } catch (error) {
      logger.error(`Failed to check backup password: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to check backup password',
      }, 500);
    }
  }

  // ============ Backup Schedule Endpoints ============

  /**
   * List all backup schedules
   */
  private async handleListBackupSchedulesRequest(): Promise<Response> {
    try {
      const schedules = this.oneboxRef.backupScheduler.getAllSchedules();
      return this.jsonResponse({ success: true, data: schedules });
    } catch (error) {
      logger.error(`Failed to list backup schedules: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to list backup schedules',
      }, 500);
    }
  }

  /**
   * Create a new backup schedule
   */
  private async handleCreateBackupScheduleRequest(req: Request): Promise<Response> {
    try {
      const body = await req.json() as IBackupScheduleCreate;

      // Validate scope type
      if (!body.scopeType) {
        return this.jsonResponse({
          success: false,
          error: 'Scope type is required (all, pattern, or service)',
        }, 400);
      }

      if (!['all', 'pattern', 'service'].includes(body.scopeType)) {
        return this.jsonResponse({
          success: false,
          error: 'Invalid scope type. Must be: all, pattern, or service',
        }, 400);
      }

      // Validate scope-specific requirements
      if (body.scopeType === 'service' && !body.serviceName) {
        return this.jsonResponse({
          success: false,
          error: 'Service name is required for service-specific schedules',
        }, 400);
      }

      if (body.scopeType === 'pattern' && !body.scopePattern) {
        return this.jsonResponse({
          success: false,
          error: 'Scope pattern is required for pattern-based schedules',
        }, 400);
      }

      if (!body.cronExpression) {
        return this.jsonResponse({
          success: false,
          error: 'Cron expression is required',
        }, 400);
      }

      if (!body.retention) {
        return this.jsonResponse({
          success: false,
          error: 'Retention policy is required',
        }, 400);
      }

      // Validate retention policy
      const { hourly, daily, weekly, monthly } = body.retention;
      if (typeof hourly !== 'number' || typeof daily !== 'number' ||
          typeof weekly !== 'number' || typeof monthly !== 'number') {
        return this.jsonResponse({
          success: false,
          error: 'Retention policy must have hourly, daily, weekly, and monthly as numbers',
        }, 400);
      }

      if (hourly < 0 || daily < 0 || weekly < 0 || monthly < 0) {
        return this.jsonResponse({
          success: false,
          error: 'Retention values must be non-negative',
        }, 400);
      }

      const schedule = await this.oneboxRef.backupScheduler.createSchedule(body);

      // Build descriptive message based on scope type
      let scopeDesc: string;
      switch (body.scopeType) {
        case 'all':
          scopeDesc = 'all services';
          break;
        case 'pattern':
          scopeDesc = `pattern '${body.scopePattern}'`;
          break;
        case 'service':
          scopeDesc = `service '${body.serviceName}'`;
          break;
      }

      return this.jsonResponse({
        success: true,
        message: `Backup schedule created for ${scopeDesc}`,
        data: schedule,
      });
    } catch (error) {
      logger.error(`Failed to create backup schedule: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to create backup schedule',
      }, 500);
    }
  }

  /**
   * Get a specific backup schedule
   */
  private async handleGetBackupScheduleRequest(scheduleId: number): Promise<Response> {
    try {
      const schedule = this.oneboxRef.backupScheduler.getScheduleById(scheduleId);
      if (!schedule) {
        return this.jsonResponse({ success: false, error: 'Backup schedule not found' }, 404);
      }

      return this.jsonResponse({ success: true, data: schedule });
    } catch (error) {
      logger.error(`Failed to get backup schedule ${scheduleId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to get backup schedule',
      }, 500);
    }
  }

  /**
   * Update a backup schedule
   */
  private async handleUpdateBackupScheduleRequest(scheduleId: number, req: Request): Promise<Response> {
    try {
      const body = await req.json() as IBackupScheduleUpdate;

      // Validate retention policy if provided
      if (body.retention) {
        const { hourly, daily, weekly, monthly } = body.retention;
        if (typeof hourly !== 'number' || typeof daily !== 'number' ||
            typeof weekly !== 'number' || typeof monthly !== 'number') {
          return this.jsonResponse({
            success: false,
            error: 'Retention policy must have hourly, daily, weekly, and monthly as numbers',
          }, 400);
        }
        if (hourly < 0 || daily < 0 || weekly < 0 || monthly < 0) {
          return this.jsonResponse({
            success: false,
            error: 'Retention values must be non-negative',
          }, 400);
        }
      }

      const schedule = await this.oneboxRef.backupScheduler.updateSchedule(scheduleId, body);

      return this.jsonResponse({
        success: true,
        message: 'Backup schedule updated',
        data: schedule,
      });
    } catch (error) {
      logger.error(`Failed to update backup schedule ${scheduleId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to update backup schedule',
      }, 500);
    }
  }

  /**
   * Delete a backup schedule
   */
  private async handleDeleteBackupScheduleRequest(scheduleId: number): Promise<Response> {
    try {
      await this.oneboxRef.backupScheduler.deleteSchedule(scheduleId);

      return this.jsonResponse({
        success: true,
        message: 'Backup schedule deleted',
      });
    } catch (error) {
      logger.error(`Failed to delete backup schedule ${scheduleId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to delete backup schedule',
      }, 500);
    }
  }

  /**
   * Trigger immediate backup for a schedule
   */
  private async handleTriggerBackupScheduleRequest(scheduleId: number): Promise<Response> {
    try {
      await this.oneboxRef.backupScheduler.triggerBackup(scheduleId);

      return this.jsonResponse({
        success: true,
        message: 'Backup triggered successfully',
      });
    } catch (error) {
      logger.error(`Failed to trigger backup for schedule ${scheduleId}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to trigger backup',
      }, 500);
    }
  }

  /**
   * List backup schedules for a specific service
   */
  private async handleListServiceBackupSchedulesRequest(serviceName: string): Promise<Response> {
    try {
      const service = this.oneboxRef.services.getService(serviceName);
      if (!service) {
        return this.jsonResponse({ success: false, error: 'Service not found' }, 404);
      }

      const schedules = this.oneboxRef.backupScheduler.getSchedulesForService(serviceName);
      return this.jsonResponse({ success: true, data: schedules });
    } catch (error) {
      logger.error(`Failed to list backup schedules for service ${serviceName}: ${getErrorMessage(error)}`);
      return this.jsonResponse({
        success: false,
        error: getErrorMessage(error) || 'Failed to list backup schedules',
      }, 500);
    }
  }

  /**
   * Helper to create JSON response
   */
  private jsonResponse(data: IApiResponse, status = 200): Response {
    return new Response(JSON.stringify(data), {
      status,
      headers: { 'Content-Type': 'application/json' },
    });
  }
}
