/**
 * CoreTraffic Manager for Onebox
 *
 * Manages CoreTraffic as a Docker Swarm service so it can route to services on
 * the Onebox overlay network.
 */

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

export const CORETRAFFIC_SERVICE_NAME = 'onebox-coretraffic';
const legacyProxyServiceNames = ['onebox-smartproxy', 'onebox-caddy'];
const CORETRAFFIC_IMAGE = 'code.foss.global/serve.zone/coretraffic:latest';
const CORETRAFFIC_ADMIN_CONTAINER_PORT = 3000;
const CORETRAFFIC_HTTP_CONTAINER_PORT = 80;
const CORETRAFFIC_HTTPS_CONTAINER_PORT = 443;
const CORETRAFFIC_TASK_START_MAX_ATTEMPTS = 300;
const CORETRAFFIC_OBSERVATION_TIMEOUT_MS = 3000;
const CORETRAFFIC_OBSERVATION_FRESHNESS_MS = 1000;
const reservedPublicPortKeys = new Set(['tcp:80', 'tcp:443', 'udp:443']);

export interface ICoreTrafficRoute {
  domain: string;
  upstream: string;
}

export interface ICoreTrafficPublicPortRoute {
  name: string;
  listenPort: number;
  transport: 'tcp' | 'udp';
  targetHost: string;
  targetPort: number;
}

export interface ICoreTrafficCertificate {
  domain: string;
  certPem: string;
  keyPem: string;
}

export interface ICoreTrafficStatusObservation {
  state: 'starting' | 'running' | 'stopped' | 'failed' | 'unknown';
  running: boolean | null;
  observedAt: number;
  httpPort: number;
  httpsPort: number;
  routes: number;
  certificates: number;
}

interface ICoreTrafficTask {
  DesiredState?: string;
  Status?: {
    State?: string;
    Timestamp?: string;
  };
  UpdatedAt?: string;
  CreatedAt?: string;
}

interface ICoreTrafficRouteConfig {
  name: string;
  match: {
    ports: number;
    domains?: string;
    protocol?: 'http' | 'tcp' | 'udp' | 'quic' | 'http3';
    transport?: 'tcp' | 'udp' | 'all';
  };
  action: {
    type: 'forward';
    targets: Array<{ host: string; port: number }>;
    tls?: {
      mode: 'terminate';
      certificate: {
        key: string;
        cert: string;
      };
    };
    websocket?: {
      enabled: boolean;
    };
  };
  priority?: number;
}

interface ICoreTrafficManagerOptions {
  certsDir?: string;
  adminPort?: number;
  httpPort?: number;
  httpsPort?: number;
  observationTimeoutMs?: number;
  getAdminToken?: () => Promise<string>;
}

interface ICoreTrafficStopOptions {
  throwOnError?: boolean;
}

export class CoreTrafficManager {
  private dockerClient: InstanceType<typeof plugins.docker.Docker> | null = null;
  private dockerClientInitializationPromise: Promise<void> | null = null;
  private certsDir: string;
  private adminUrl: string;
  private adminPort: number;
  private httpPort: number;
  private httpsPort: number;
  private routes: Map<string, ICoreTrafficRoute> = new Map();
  private publicPortRoutes: Map<string, ICoreTrafficPublicPortRoute> = new Map();
  private certificates: Map<string, ICoreTrafficCertificate> = new Map();
  private networkName = 'onebox-network';
  private serviceRunning = false;
  private observationPromise: Promise<ICoreTrafficStatusObservation> | null = null;
  private lastObservation: ICoreTrafficStatusObservation | null = null;
  private observationTimeoutMs: number;
  private adminToken: string | null = null;
  private getAdminTokenFromOptions?: () => Promise<string>;

  constructor(options?: ICoreTrafficManagerOptions) {
    this.certsDir = options?.certsDir || './.nogit/certs';
    this.adminPort = options?.adminPort || 2019;
    this.adminUrl = `http://localhost:${this.adminPort}`;
    this.httpPort = options?.httpPort || 8080;
    this.httpsPort = options?.httpsPort || 8443;
    this.observationTimeoutMs = options?.observationTimeoutMs || CORETRAFFIC_OBSERVATION_TIMEOUT_MS;
    this.getAdminTokenFromOptions = options?.getAdminToken;
  }

  private async ensureDockerClient(): Promise<void> {
    if (this.dockerClient) return;
    if (this.dockerClientInitializationPromise) {
      return await this.dockerClientInitializationPromise;
    }

    const candidate = new plugins.docker.Docker({
      socketPath: 'unix:///var/run/docker.sock',
    });
    let initializationPromise!: Promise<void>;
    initializationPromise = this.withTimeout(
      candidate.start(),
      'Docker initialization',
    )
      .then(() => {
        this.dockerClient = candidate;
      })
      .catch((error) => {
        void candidate.stop().catch(() => {});
        throw error;
      })
      .finally(() => {
        if (this.dockerClientInitializationPromise === initializationPromise) {
          this.dockerClientInitializationPromise = null;
        }
      });
    this.dockerClientInitializationPromise = initializationPromise;
    return await initializationPromise;
  }

  private async withTimeout<T>(promiseArg: Promise<T>, labelArg: string): Promise<T> {
    let timeoutHandle: ReturnType<typeof setTimeout> | null = null;
    const timeoutPromise = new Promise<never>((_resolve, reject) => {
      timeoutHandle = setTimeout(
        () => reject(new Error(`${labelArg} timed out`)),
        this.observationTimeoutMs,
      );
    });
    // Promise.race observes both inputs, including a late Docker rejection.
    try {
      return await Promise.race([promiseArg, timeoutPromise]);
    } finally {
      if (timeoutHandle) {
        clearTimeout(timeoutHandle);
      }
    }
  }

  private createObservation(
    stateArg: ICoreTrafficStatusObservation['state'],
  ): ICoreTrafficStatusObservation {
    const running = stateArg === 'running'
      ? true
      : stateArg === 'stopped' || stateArg === 'failed'
        ? false
        : null;
    if (running !== null) {
      this.serviceRunning = running;
    }
    return {
      state: stateArg,
      running,
      observedAt: Date.now(),
      httpPort: this.httpPort,
      httpsPort: this.httpsPort,
      routes: this.routes.size,
      certificates: this.certificates.size,
    };
  }

  private classifyTaskState(
    tasksArg: ICoreTrafficTask[],
    desiredReplicasArg: number | null,
  ): ICoreTrafficStatusObservation['state'] {
    const currentTasks = tasksArg
      .filter((taskArg) => !['shutdown', 'remove'].includes(taskArg.DesiredState || ''))
      .sort((taskA, taskB) => {
        const timestampA = Date.parse(
          taskA?.Status?.Timestamp || taskA?.UpdatedAt || taskA?.CreatedAt || '',
        ) || 0;
        const timestampB = Date.parse(
          taskB?.Status?.Timestamp || taskB?.UpdatedAt || taskB?.CreatedAt || '',
        ) || 0;
        return timestampB - timestampA;
      });
    const states = currentTasks
      .map((taskArg) => taskArg.Status?.State)
      .filter((stateArg): stateArg is string => typeof stateArg === 'string');
    if (states.includes('running')) {
      return 'running';
    }
    if (
      states.some((stateArg) =>
        ['new', 'allocated', 'pending', 'assigned', 'accepted', 'preparing', 'ready', 'starting']
          .includes(stateArg)
      )
    ) {
      return 'starting';
    }
    if (desiredReplicasArg === 0) {
      return 'stopped';
    }
    if (['failed', 'rejected', 'orphaned'].includes(states[0])) {
      return 'failed';
    }
    return 'unknown';
  }

  private async performStatusObservation(): Promise<ICoreTrafficStatusObservation> {
    try {
      await this.ensureDockerClient();
      const serviceResponse = await this.withTimeout(
        this.dockerClient!.request('GET', `/services/${CORETRAFFIC_SERVICE_NAME}`, {}),
        'CoreTraffic service lookup',
      );
      if (serviceResponse.statusCode === 404) {
        return this.createObservation('stopped');
      }
      if (
        serviceResponse.statusCode !== 200
        || !serviceResponse.body
        || typeof serviceResponse.body !== 'object'
        || typeof serviceResponse.body.ID !== 'string'
      ) {
        return this.createObservation('unknown');
      }

      const desiredReplicasValue = serviceResponse.body.Spec?.Mode?.Replicated?.Replicas;
      const desiredReplicas = Number.isInteger(desiredReplicasValue)
        ? desiredReplicasValue as number
        : null;
      const tasksResponse = await this.withTimeout(
        this.dockerClient!.request(
          'GET',
          `/tasks?filters=${encodeURIComponent(JSON.stringify({ service: [serviceResponse.body.ID] }))}`,
          {},
        ),
        'CoreTraffic task lookup',
      );
      if (tasksResponse.statusCode !== 200 || !Array.isArray(tasksResponse.body)) {
        return this.createObservation('unknown');
      }
      return this.createObservation(
        this.classifyTaskState(tasksResponse.body, desiredReplicas),
      );
    } catch (error) {
      logger.warn(`Unable to observe CoreTraffic runtime status: ${getErrorMessage(error)}`);
      return this.createObservation('unknown');
    }
  }

  public async observeStatus(): Promise<ICoreTrafficStatusObservation> {
    if (
      this.lastObservation
      && Date.now() - this.lastObservation.observedAt < CORETRAFFIC_OBSERVATION_FRESHNESS_MS
    ) {
      return this.lastObservation;
    }
    if (this.observationPromise) {
      return this.observationPromise;
    }

    let observationPromise!: Promise<ICoreTrafficStatusObservation>;
    observationPromise = this.performStatusObservation()
      .then((observationArg) => {
        this.lastObservation = observationArg;
        return observationArg;
      })
      .finally(() => {
        if (this.observationPromise === observationPromise) {
          this.observationPromise = null;
        }
      });
    this.observationPromise = observationPromise;
    return observationPromise;
  }

  setPorts(httpPort: number, httpsPort: number): void {
    this.httpPort = httpPort;
    this.httpsPort = httpsPort;
  }

  async start(): Promise<void> {
    if (this.serviceRunning) {
      logger.warn('CoreTraffic service is already running');
      return;
    }

    let createdService = false;

    try {
      await this.ensureDockerClient();
      await plugins.fs.mkdir(this.certsDir, { recursive: true });

      logger.info('Starting CoreTraffic Docker service...');

      for (const legacyServiceName of legacyProxyServiceNames) {
        const legacyService = await this.getExistingService(legacyServiceName);
        if (legacyService) {
          logger.info(
            `Legacy proxy service ${legacyServiceName} exists, removing it before CoreTraffic startup...`,
          );
          await this.removeService(legacyServiceName);
          await new Promise((resolve) => setTimeout(resolve, 2000));
        }
      }

      const existingService = await this.getExistingService();
      if (existingService) {
        logger.info('CoreTraffic service exists, removing old service...');
        await this.removeService();
        await new Promise((resolve) => setTimeout(resolve, 2000));
      }

      const networkId = await this.getNetworkId();
      const adminToken = await this.getAdminToken();

      const response = await this.dockerClient!.request('POST', '/services/create', {
        Name: CORETRAFFIC_SERVICE_NAME,
        Labels: {
          'managed-by': 'onebox',
          'onebox-type': 'coretraffic',
        },
        TaskTemplate: {
          ContainerSpec: {
            Image: CORETRAFFIC_IMAGE,
            Env: [
              'CORETRAFFIC_MODE=standalone',
              'CORETRAFFIC_ADMIN_HOST=0.0.0.0',
              `CORETRAFFIC_ADMIN_PORT=${CORETRAFFIC_ADMIN_CONTAINER_PORT}`,
              `CORETRAFFIC_ADMIN_TOKEN=${adminToken}`,
            ],
          },
          Networks: [
            {
              Target: networkId,
            },
          ],
          RestartPolicy: {
            Condition: 'any',
            MaxAttempts: 0,
          },
        },
        Mode: {
          Replicated: {
            Replicas: 1,
          },
        },
        EndpointSpec: {
          Ports: this.buildEndpointPorts(),
        },
      });

      if (response.statusCode >= 300) {
        throw new Error(`Failed to create CoreTraffic service: HTTP ${response.statusCode} - ${JSON.stringify(response.body)}`);
      }

      const serviceId = response.body.ID;
      createdService = true;
      logger.info(`CoreTraffic service created: ${serviceId}`);

      await this.waitForServiceTaskRunning(serviceId);
      await this.waitForReady();
      this.serviceRunning = true;
      await this.reloadConfig({ skipRunningCheck: true });

      logger.success(`CoreTraffic started (HTTP: ${this.httpPort}, HTTPS: ${this.httpsPort}, Admin: ${this.adminUrl})`);
    } catch (error) {
      this.serviceRunning = false;

      if (createdService) {
        logger.warn('Cleaning up CoreTraffic service after failed startup...');
        try {
          await this.removeService();
        } catch (cleanupError) {
          logger.error(`Failed to clean up CoreTraffic service after failed startup: ${getErrorMessage(cleanupError)}`);
        }
      }

      if (this.dockerClient) {
        try {
          await this.dockerClient.stop();
        } catch (stopError) {
          logger.error(`Failed to stop CoreTraffic Docker client after failed startup: ${getErrorMessage(stopError)}`);
        } finally {
          this.dockerClient = null;
        }
      }

      logger.error(`Failed to start CoreTraffic: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  private async getAdminToken(): Promise<string> {
    if (this.adminToken) {
      return this.adminToken;
    }

    this.adminToken = this.getAdminTokenFromOptions
      ? await this.getAdminTokenFromOptions()
      : `obct_${plugins.crypto.randomUUID().replaceAll('-', '')}${plugins.crypto.randomUUID().replaceAll('-', '')}`;
    return this.adminToken;
  }

  private async getAdminHeaders(): Promise<Record<string, string>> {
    return {
      Authorization: `Bearer ${await this.getAdminToken()}`,
    };
  }

  private async discardResponseBody(response: Response): Promise<void> {
    try {
      await response.arrayBuffer();
    } catch {
      // Response cleanup must not mask the original health/admin outcome.
    }
  }

  private async getExistingService(serviceNameArg = CORETRAFFIC_SERVICE_NAME): Promise<any | null> {
    try {
      const response = await this.dockerClient!.request('GET', `/services/${serviceNameArg}`, {});
      if (response.statusCode === 200) {
        return response.body;
      }
      return null;
    } catch {
      return null;
    }
  }

  private async removeService(serviceNameArg = CORETRAFFIC_SERVICE_NAME): Promise<void> {
    try {
      const response = await this.dockerClient!.request('DELETE', `/services/${serviceNameArg}`, {});
      if (response.statusCode >= 300 && response.statusCode !== 404) {
        throw new Error(`Failed to remove CoreTraffic service ${serviceNameArg}: HTTP ${response.statusCode} - ${JSON.stringify(response.body)}`);
      }
    } catch (error) {
      const message = getErrorMessage(error).toLowerCase();
      if (message.includes('404') || message.includes('not found') || message.includes('no such service')) {
        return;
      }
      throw error;
    }
  }

  private async getNetworkId(): Promise<string> {
    const networks = await this.dockerClient!.listNetworks();
    const network = networks.find((n: any) => n.Name === this.networkName);
    if (!network) {
      throw new Error(`Network not found: ${this.networkName}`);
    }
    return network.Id;
  }

  private async waitForReady(maxAttempts = 10, intervalMs = 1000): Promise<void> {
    for (let i = 0; i < maxAttempts; i++) {
      try {
        const response = await fetch(`${this.adminUrl}/ready`);
        const ready = response.ok;
        await this.discardResponseBody(response);
        if (ready) {
          return;
        }
      } catch {
        // Not ready yet.
      }
      await new Promise((resolve) => setTimeout(resolve, intervalMs));
    }
    throw new Error('CoreTraffic service failed to start within timeout');
  }

  private async waitForServiceTaskRunning(
    serviceIdArg: string,
    maxAttempts = CORETRAFFIC_TASK_START_MAX_ATTEMPTS,
    intervalMs = 1000,
  ): Promise<void> {
    let lastState = 'unknown';

    for (let i = 0; i < maxAttempts; i++) {
      const tasksResponse = await this.dockerClient!.request(
        'GET',
        `/tasks?filters=${encodeURIComponent(JSON.stringify({ service: [serviceIdArg] }))}`,
        {},
      );

      if (tasksResponse.statusCode === 200 && Array.isArray(tasksResponse.body)) {
        const tasks = tasksResponse.body;
        const runningTask = tasks.find((task: any) => task.Status?.State === 'running');
        if (runningTask) {
          return;
        }

        const latestTask = tasks[0];
        lastState = latestTask?.Status?.State || lastState;
      }

      await new Promise((resolve) => setTimeout(resolve, intervalMs));
    }

    throw new Error(`CoreTraffic service task did not reach running state (last state: ${lastState})`);
  }

  async stop(optionsArg: ICoreTrafficStopOptions = {}): Promise<void> {
    let stopError: unknown;
    try {
      await this.ensureDockerClient();

      if (!this.serviceRunning && !(await this.getExistingService())) {
        return;
      }

      logger.info('Stopping CoreTraffic service...');
      await this.removeService();

      this.serviceRunning = false;
      logger.info('CoreTraffic service stopped');
    } catch (error) {
      stopError = error;
      logger.error(`Failed to stop CoreTraffic: ${getErrorMessage(error)}`);
    } finally {
      if (this.dockerClient) {
        try {
          await this.dockerClient.stop();
        } catch (error) {
          logger.error(`Failed to stop CoreTraffic Docker client: ${getErrorMessage(error)}`);
        } finally {
          this.dockerClient = null;
        }
      }
    }
    if (stopError && optionsArg.throwOnError) {
      throw stopError;
    }
  }

  async isHealthy(): Promise<boolean> {
    try {
      const response = await fetch(`${this.adminUrl}/health`);
      const healthy = response.ok;
      await this.discardResponseBody(response);
      return healthy;
    } catch {
      return false;
    }
  }

  async isRunning(): Promise<boolean> {
    try {
      await this.ensureDockerClient();
      const service = await this.getExistingService();
      if (!service) return false;

      const tasksResponse = await this.dockerClient!.request(
        'GET',
        `/tasks?filters=${encodeURIComponent(JSON.stringify({ service: [CORETRAFFIC_SERVICE_NAME] }))}`,
        {},
      );

      if (tasksResponse.statusCode !== 200) return false;

      const tasks = tasksResponse.body;
      return tasks.some((task: any) => task.Status?.State === 'running');
    } catch {
      return false;
    }
  }

  private routeName(prefixArg: string, domainArg: string): string {
    return `${prefixArg}-${domainArg.replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-|-$/g, '')}`;
  }

  private buildEndpointPorts(): Array<{
    Protocol: string;
    TargetPort: number;
    PublishedPort: number;
    PublishMode: 'host';
  }> {
    const endpointPorts = [
      {
        Protocol: 'tcp',
        TargetPort: CORETRAFFIC_HTTP_CONTAINER_PORT,
        PublishedPort: this.httpPort,
        PublishMode: 'host' as const,
      },
      {
        Protocol: 'tcp',
        TargetPort: CORETRAFFIC_HTTPS_CONTAINER_PORT,
        PublishedPort: this.httpsPort,
        PublishMode: 'host' as const,
      },
      {
        Protocol: 'tcp',
        TargetPort: CORETRAFFIC_ADMIN_CONTAINER_PORT,
        PublishedPort: this.adminPort,
        PublishMode: 'host' as const,
      },
    ];
    const seenPorts = new Set(endpointPorts.map((portArg) => `${portArg.Protocol}:${portArg.PublishedPort}`));
    for (const route of this.publicPortRoutes.values()) {
      const key = this.publicPortRouteKey(route);
      if (reservedPublicPortKeys.has(key)) {
        throw new Error(`CoreTraffic public port conflict: ${route.listenPort}/${route.transport}`);
      }
      if (seenPorts.has(key)) {
        throw new Error(`CoreTraffic public port conflict: ${route.listenPort}/${route.transport}`);
      }
      seenPorts.add(key);
      endpointPorts.push({
        Protocol: route.transport,
        TargetPort: route.listenPort,
        PublishedPort: route.listenPort,
        PublishMode: 'host' as const,
      });
    }
    return endpointPorts;
  }

  private publicPortRouteKey(routeArg: Pick<ICoreTrafficPublicPortRoute, 'listenPort' | 'transport'>): string {
    return `${routeArg.transport}:${routeArg.listenPort}`;
  }

  private publicPortRouteKeySnapshot(): string {
    return Array.from(this.publicPortRoutes.values())
      .map((routeArg) => this.publicPortRouteKey(routeArg))
      .sort()
      .join('|');
  }

  private buildPublicPortRouteMap(routesArg: ICoreTrafficPublicPortRoute[]): Map<string, ICoreTrafficPublicPortRoute> {
    const nextRoutes = new Map<string, ICoreTrafficPublicPortRoute>();
    for (const route of routesArg) {
      const key = this.publicPortRouteKey(route);
      if (nextRoutes.has(key)) {
        throw new Error(`Duplicate CoreTraffic public route ${key}`);
      }
      nextRoutes.set(key, route);
    }
    return nextRoutes;
  }

  validatePublicPortRoutes(routesArg: ICoreTrafficPublicPortRoute[]): void {
    const previousRoutes = this.publicPortRoutes;
    this.publicPortRoutes = this.buildPublicPortRouteMap(routesArg);
    try {
      this.buildEndpointPorts();
    } finally {
      this.publicPortRoutes = previousRoutes;
    }
  }

  private parseUpstream(upstreamArg: string): { host: string; port: number } {
    const separatorIndex = upstreamArg.lastIndexOf(':');
    if (separatorIndex <= 0 || separatorIndex === upstreamArg.length - 1) {
      throw new Error(`Invalid upstream target: ${upstreamArg}`);
    }

    const host = upstreamArg.slice(0, separatorIndex);
    const port = Number(upstreamArg.slice(separatorIndex + 1));
    if (!Number.isInteger(port) || port < 1 || port > 65535) {
      throw new Error(`Invalid upstream port in target: ${upstreamArg}`);
    }

    return { host, port };
  }

  private buildRoutes(): ICoreTrafficRouteConfig[] {
    const routeConfigs: ICoreTrafficRouteConfig[] = [];

    for (const [domain, route] of this.routes) {
      const target = this.parseUpstream(route.upstream);
      const baseAction = {
        type: 'forward' as const,
        targets: [target],
        websocket: {
          enabled: true,
        },
      };

      routeConfigs.push({
        name: this.routeName('http', domain),
        match: {
          ports: CORETRAFFIC_HTTP_CONTAINER_PORT,
          domains: domain,
          protocol: 'http',
        },
        action: baseAction,
        priority: 10,
      });

      const certificate = this.certificates.get(domain);
      if (certificate) {
        routeConfigs.push({
          name: this.routeName('https', domain),
          match: {
            ports: CORETRAFFIC_HTTPS_CONTAINER_PORT,
            domains: domain,
            protocol: 'http',
          },
          action: {
            ...baseAction,
            tls: {
              mode: 'terminate',
              certificate: {
                key: certificate.keyPem,
                cert: certificate.certPem,
              },
            },
          },
          priority: 20,
        });
      }
    }

    for (const route of this.publicPortRoutes.values()) {
      routeConfigs.push({
        name: `public-${route.name}`.replace(/[^a-zA-Z0-9-]+/g, '-').slice(0, 96),
        match: {
          ports: route.listenPort,
          transport: route.transport,
        },
        action: {
          type: 'forward',
          targets: [{ host: route.targetHost, port: route.targetPort }],
        },
        priority: 30,
      });
    }

    return routeConfigs;
  }

  async setPublicPortRoutes(routesArg: ICoreTrafficPublicPortRoute[]): Promise<void> {
    const previousKeySnapshot = this.publicPortRouteKeySnapshot();
    const previousRoutes = this.publicPortRoutes;
    const nextRoutes = this.buildPublicPortRouteMap(routesArg);
    this.publicPortRoutes = nextRoutes;
    try {
      this.buildEndpointPorts();
    } catch (error) {
      this.publicPortRoutes = previousRoutes;
      throw error;
    }
    const nextKeySnapshot = this.publicPortRouteKeySnapshot();

    if (!this.serviceRunning) return;
    if (previousKeySnapshot !== nextKeySnapshot) {
      logger.info('CoreTraffic public port set changed; restarting service to apply host listeners');
      let stopped = false;
      try {
        await this.stop({ throwOnError: true });
        stopped = true;
        await this.start();
      } catch (error) {
        if (!stopped) {
          this.publicPortRoutes = previousRoutes;
        }
        throw error;
      }
      return;
    }
    await this.reloadConfig();
  }

  async reloadConfig(options: { skipRunningCheck?: boolean } = {}): Promise<void> {
    if (!options.skipRunningCheck) {
      const isRunning = await this.isRunning();
      if (!isRunning) {
        logger.warn('CoreTraffic not running, cannot reload config');
        return;
      }
    }

    const routes = this.buildRoutes();

    try {
      const response = await fetch(`${this.adminUrl}/routes`, {
        method: 'PUT',
        headers: {
          ...(await this.getAdminHeaders()),
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ routes }),
      });

      if (!response.ok) {
        const text = await response.text();
        throw new Error(`Failed to reload CoreTraffic routes: ${response.status} ${text}`);
      }

      await this.discardResponseBody(response);

      logger.debug('CoreTraffic routes reloaded');
    } catch (error) {
      logger.error(`Failed to reload CoreTraffic routes: ${getErrorMessage(error)}`);
      throw error;
    }
  }

  async addRoute(domain: string, upstream: string): Promise<void> {
    this.routes.set(domain, { domain, upstream });

    if (await this.isRunning()) {
      await this.reloadConfig();
    }

    logger.success(`Added CoreTraffic route: ${domain} -> ${upstream}`);
  }

  async removeRoute(domain: string): Promise<void> {
    if (this.routes.delete(domain)) {
      if (await this.isRunning()) {
        await this.reloadConfig();
      }
      logger.success(`Removed CoreTraffic route: ${domain}`);
    }
  }

  async clearPublicPortRoutes(): Promise<void> {
    await this.setPublicPortRoutes([]);
  }

  async addCertificate(domain: string, certPem: string, keyPem: string): Promise<void> {
    this.certificates.set(domain, {
      domain,
      certPem,
      keyPem,
    });

    try {
      await plugins.fs.mkdir(this.certsDir, { recursive: true });
      await plugins.fs.writeTextFile(`${this.certsDir}/${domain}.crt`, certPem);
      await plugins.fs.writeTextFile(`${this.certsDir}/${domain}.key`, keyPem);
    } catch (error) {
      logger.warn(`Failed to write certificate backup for ${domain}: ${getErrorMessage(error)}`);
    }

    if (await this.isRunning()) {
      await this.reloadConfig();
    }

    logger.success(`Added TLS certificate for ${domain}`);
  }

  async removeCertificate(domain: string): Promise<void> {
    if (this.certificates.delete(domain)) {
      try {
        await plugins.fs.remove(`${this.certsDir}/${domain}.crt`);
        await plugins.fs.remove(`${this.certsDir}/${domain}.key`);
      } catch {
        // Files may not exist.
      }

      if (await this.isRunning()) {
        await this.reloadConfig();
      }

      logger.success(`Removed TLS certificate for ${domain}`);
    }
  }

  getRoutes(): ICoreTrafficRoute[] {
    return Array.from(this.routes.values());
  }

  getCertificates(): ICoreTrafficCertificate[] {
    return Array.from(this.certificates.values());
  }

  clear(): void {
    this.routes.clear();
    this.publicPortRoutes.clear();
    this.certificates.clear();
  }

  getStatus(): {
    running: boolean;
    httpPort: number;
    httpsPort: number;
    routes: number;
    certificates: number;
  } {
    return {
      running: this.serviceRunning,
      httpPort: this.httpPort,
      httpsPort: this.httpsPort,
      routes: this.routes.size,
      certificates: this.certificates.size,
    };
  }
}
