import * as plugins from '../../plugins.js';
import type { IContainerLogStreamHandle } from '../../classes/docker.js';
import { logger } from '../../logging.js';
import { getErrorMessage } from '../../utils/error.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
import { requireAdminIdentity } from '../helpers/guards.js';

type TTelemetryTarget = interfaces.requests.TTelemetrySubscriptionTarget;

interface ITelemetrySubscription {
  id: string;
  peerId: string;
  target: TTelemetryTarget;
  targetKey: string;
  expiresAt: number;
}

interface ILogWorker {
  target: TTelemetryTarget;
  targetKey: string;
  handle: IContainerLogStreamHandle | null;
  containerId: string | null;
  starting: boolean;
  startingGeneration: number | null;
  intentionallyClosing: boolean;
  retryTimeout: ReturnType<typeof setTimeout> | null;
  generation: number;
}

interface IMetricWorker {
  target: TTelemetryTarget;
  targetKey: string;
  intervalId: ReturnType<typeof setInterval>;
  inFlight: boolean;
}

interface IQueuedLogEntry {
  line: string;
  isError: boolean;
}

interface ILogPushQueue {
  target: TTelemetryTarget;
  targetKey: string;
  entries: IQueuedLogEntry[];
  flushing: boolean;
  droppedEntries: number;
}

interface ITelemetryPeer {
  id: string;
  readyState: number;
  send: (dataArg: string) => void;
}

export class TelemetryHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();

  private subscriptions = new Map<string, ITelemetrySubscription>();
  private logWorkers = new Map<string, ILogWorker>();
  private metricWorkers = new Map<string, IMetricWorker>();
  private logPushQueues = new Map<string, ILogPushQueue>();
  private peerPushQueues = new Map<string, Promise<void>>();
  private sweepInterval: ReturnType<typeof setInterval> | null = null;
  private readonly leaseMs = 90_000;
  private readonly sweepIntervalMs = 15_000;
  private readonly metricsIntervalMs = 5_000;
  private readonly maxQueuedLogEntries = 500;
  private readonly pushTimeoutMs = 5_000;

  constructor(private opsServerRef: OpsServer) {
    this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
    this.registerHandlers();
    this.startSweepLoop();
  }

  private registerHandlers(): void {
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_SubscribeTelemetry>(
        'subscribeTelemetry',
        async (dataArg, typedToolsArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          await this.validateTarget(dataArg.target);
          const peerId = this.getPeerId(typedToolsArg);
          const existingSubscription = dataArg.subscriptionId
            ? this.subscriptions.get(dataArg.subscriptionId)
            : undefined;
          if (existingSubscription && existingSubscription.peerId !== peerId) {
            throw new plugins.typedrequest.TypedResponseError('Telemetry subscription is owned by another peer');
          }
          const subscriptionId = existingSubscription?.id || crypto.randomUUID();
          const leaseExpiresAt = Date.now() + this.leaseMs;
          const targetKey = this.getTargetKey(dataArg.target);
          const previousTargetKey = existingSubscription?.targetKey;

          this.subscriptions.set(subscriptionId, {
            id: subscriptionId,
            peerId,
            target: dataArg.target,
            targetKey,
            expiresAt: leaseExpiresAt,
          });
          if (previousTargetKey && previousTargetKey !== targetKey) {
            this.cleanupTargetIfUnused(previousTargetKey);
          }

          await this.ensureTargetWorker(dataArg.target);
          if (dataArg.target.type === 'serviceMetrics' || dataArg.target.type === 'platformServiceMetrics') {
            await this.pushCurrentMetric(dataArg.target);
          }

          return { subscriptionId, leaseExpiresAt };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UnsubscribeTelemetry>(
        'unsubscribeTelemetry',
        async (dataArg, typedToolsArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const peerId = this.getPeerId(typedToolsArg);
          const subscription = this.subscriptions.get(dataArg.subscriptionId);
          if (subscription && subscription.peerId === peerId) {
            this.removeSubscription(dataArg.subscriptionId);
          }
          return { ok: true };
        },
      ),
    );

    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_UnsubscribeAllTelemetry>(
        'unsubscribeAllTelemetry',
        async (dataArg, typedToolsArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          this.removePeerSubscriptions(this.getPeerId(typedToolsArg));
          return { ok: true };
        },
      ),
    );
  }

  private getPeerId(typedToolsArg?: { localData?: Record<string, unknown> }): string {
    const peer = typedToolsArg?.localData?.peer as { id?: string } | undefined;
    if (!peer?.id) {
      throw new plugins.typedrequest.TypedResponseError('Telemetry subscriptions require a WebSocket connection');
    }
    return peer.id;
  }

  private startSweepLoop(): void {
    this.sweepInterval = setInterval(() => {
      if (this.subscriptions.size === 0) return;
      void this.sweepSubscriptions();
    }, this.sweepIntervalMs);
  }

  public stop(): void {
    if (this.sweepInterval) {
      clearInterval(this.sweepInterval);
      this.sweepInterval = null;
    }
    for (const worker of Array.from(this.logWorkers.values())) {
      this.closeLogWorker(worker.targetKey, true);
    }
    for (const worker of this.metricWorkers.values()) {
      clearInterval(worker.intervalId);
    }
    this.metricWorkers.clear();
    this.logPushQueues.clear();
    this.peerPushQueues.clear();
    this.subscriptions.clear();
  }

  private async validateTarget(targetArg: TTelemetryTarget): Promise<void> {
    switch (targetArg.type) {
      case 'serviceLogs':
      case 'serviceMetrics': {
        const service = await this.opsServerRef.oneboxRef.database.getServiceByName(targetArg.serviceName);
        if (!service) {
          throw new plugins.typedrequest.TypedResponseError(`Service not found: ${targetArg.serviceName}`);
        }
        break;
      }
      case 'platformServiceLogs':
      case 'platformServiceMetrics': {
        const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(targetArg.serviceType);
        if (!provider) {
          throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${targetArg.serviceType}`);
        }
        break;
      }
    }
  }

  private getTargetKey(targetArg: TTelemetryTarget): string {
    switch (targetArg.type) {
      case 'serviceLogs':
        return `service:${targetArg.serviceName}:logs`;
      case 'serviceMetrics':
        return `service:${targetArg.serviceName}:metrics`;
      case 'platformServiceLogs':
        return `platform:${targetArg.serviceType}:logs`;
      case 'platformServiceMetrics':
        return `platform:${targetArg.serviceType}:metrics`;
    }
  }

  private hasSubscriptionsForTarget(targetKeyArg: string): boolean {
    for (const subscription of this.subscriptions.values()) {
      if (subscription.targetKey === targetKeyArg) return true;
    }
    return false;
  }

  private getSubscriptionsForTarget(targetKeyArg: string): ITelemetrySubscription[] {
    return Array.from(this.subscriptions.values()).filter((subscription) => subscription.targetKey === targetKeyArg);
  }

  private removeSubscription(subscriptionIdArg: string): void {
    const subscription = this.subscriptions.get(subscriptionIdArg);
    if (!subscription) return;
    this.subscriptions.delete(subscriptionIdArg);
    this.cleanupTargetIfUnused(subscription.targetKey);
  }

  private removePeerSubscriptions(peerIdArg: string): void {
    const affectedTargetKeys = new Set<string>();
    for (const subscription of this.subscriptions.values()) {
      if (subscription.peerId === peerIdArg) {
        affectedTargetKeys.add(subscription.targetKey);
        this.subscriptions.delete(subscription.id);
      }
    }
    for (const targetKey of affectedTargetKeys) {
      this.cleanupTargetIfUnused(targetKey);
    }
  }

  private cleanupTargetIfUnused(targetKeyArg: string): void {
    if (this.hasSubscriptionsForTarget(targetKeyArg)) return;
    this.closeLogWorker(targetKeyArg, true);
    this.logPushQueues.delete(targetKeyArg);
    const metricWorker = this.metricWorkers.get(targetKeyArg);
    if (metricWorker) {
      clearInterval(metricWorker.intervalId);
      this.metricWorkers.delete(targetKeyArg);
    }
  }

  private async sweepSubscriptions(): Promise<void> {
    const now = Date.now();
    const activePeerIds = await this.getActivePeerIds();
    for (const subscription of Array.from(this.subscriptions.values())) {
      const peerMissing = activePeerIds ? !activePeerIds.has(subscription.peerId) : false;
      if (subscription.expiresAt <= now || peerMissing) {
        this.removeSubscription(subscription.id);
      }
    }
  }

  private async getActivePeerIds(): Promise<Set<string> | null> {
    const peers = this.getActivePeers();
    if (!peers) return null;
    return new Set(peers.map((peerArg) => peerArg.id));
  }

  private getTypedSocket(): any | null {
    return (this.opsServerRef.server as any)?.typedserver?.typedsocket || null;
  }

  private getActivePeers(): ITelemetryPeer[] | null {
    const smartServeRef = this.getTypedSocket()?.smartServeRef;
    if (!smartServeRef?.getWebSocketConnections) return null;
    return smartServeRef.getWebSocketConnections();
  }

  private getActivePeerById(peerIdArg: string): ITelemetryPeer | undefined {
    return this.getActivePeers()?.find((peerArg) => peerArg.id === peerIdArg);
  }

  private async ensureTargetWorker(targetArg: TTelemetryTarget): Promise<void> {
    if (targetArg.type === 'serviceLogs' || targetArg.type === 'platformServiceLogs') {
      await this.ensureLogWorker(targetArg);
    } else {
      this.ensureMetricWorker(targetArg);
    }
  }

  private async ensureLogWorker(targetArg: TTelemetryTarget): Promise<void> {
    const targetKey = this.getTargetKey(targetArg);
    let worker = this.logWorkers.get(targetKey);
    if (!worker) {
      worker = {
        target: targetArg,
        targetKey,
        handle: null,
        containerId: null,
        starting: false,
        startingGeneration: null,
        intentionallyClosing: false,
        retryTimeout: null,
        generation: 0,
      };
      this.logWorkers.set(targetKey, worker);
    }
    await this.startLogWorker(worker);
  }

  private async startLogWorker(workerArg: ILogWorker): Promise<void> {
    if (workerArg.handle || workerArg.starting || !this.hasSubscriptionsForTarget(workerArg.targetKey)) return;
    workerArg.starting = true;
    const generation = ++workerArg.generation;
    workerArg.startingGeneration = generation;
    try {
      const containerId = await this.resolveContainerId(workerArg.target);
      if (!containerId) return;
      workerArg.containerId = containerId;
      let streamHandle: IContainerLogStreamHandle | null = null;
      streamHandle = await this.opsServerRef.oneboxRef.docker.streamContainerLogs(
        containerId,
        (lineArg: string, isErrorArg: boolean) => {
          this.enqueueLog(workerArg.target, lineArg, isErrorArg);
        },
        {
          onClose: () => {
            if (workerArg.handle === streamHandle) {
              workerArg.handle = null;
            }
            if (this.logWorkers.get(workerArg.targetKey) === workerArg && !workerArg.intentionallyClosing) {
              this.scheduleLogWorkerRestart(workerArg.targetKey);
            }
          },
        },
      );
      if (
        this.logWorkers.get(workerArg.targetKey) !== workerArg ||
        workerArg.generation !== generation ||
        !this.hasSubscriptionsForTarget(workerArg.targetKey)
      ) {
        streamHandle.close();
        return;
      }
      workerArg.handle = streamHandle;
    } catch (error) {
      logger.debug(`Telemetry log stream unavailable for ${workerArg.targetKey}: ${getErrorMessage(error)}`);
      this.scheduleLogWorkerRestart(workerArg.targetKey);
    } finally {
      if (workerArg.startingGeneration === generation) {
        workerArg.starting = false;
        workerArg.startingGeneration = null;
      }
    }
  }

  private scheduleLogWorkerRestart(targetKeyArg: string): void {
    const worker = this.logWorkers.get(targetKeyArg);
    if (!worker || worker.retryTimeout || !this.hasSubscriptionsForTarget(targetKeyArg)) return;
    worker.retryTimeout = setTimeout(() => {
      worker.retryTimeout = null;
      void this.startLogWorker(worker);
    }, 2_000);
  }

  private closeLogWorker(targetKeyArg: string, removeArg: boolean): void {
    const worker = this.logWorkers.get(targetKeyArg);
    if (!worker) return;
    if (worker.retryTimeout) {
      clearTimeout(worker.retryTimeout);
      worker.retryTimeout = null;
    }
    worker.generation++;
    worker.starting = false;
    worker.startingGeneration = null;
    worker.intentionallyClosing = true;
    try {
      worker.handle?.close();
    } catch {
      // The Docker stream may already be closed.
    }
    worker.handle = null;
    worker.containerId = null;
    worker.intentionallyClosing = false;
    if (removeArg) {
      this.logWorkers.delete(targetKeyArg);
    }
  }

  private ensureMetricWorker(targetArg: TTelemetryTarget): void {
    const targetKey = this.getTargetKey(targetArg);
    if (this.metricWorkers.has(targetKey)) return;
    const intervalId = setInterval(() => {
      void this.runMetricWorkerTick(targetKey);
    }, this.metricsIntervalMs);
    this.metricWorkers.set(targetKey, { target: targetArg, targetKey, intervalId, inFlight: false });
  }

  private async runMetricWorkerTick(targetKeyArg: string): Promise<void> {
    const worker = this.metricWorkers.get(targetKeyArg);
    if (!worker || worker.inFlight || !this.hasSubscriptionsForTarget(targetKeyArg)) return;
    worker.inFlight = true;
    try {
      await this.pushCurrentMetric(worker.target);
    } catch (error) {
      logger.debug(`Telemetry metric update failed for ${targetKeyArg}: ${getErrorMessage(error)}`);
    } finally {
      const currentWorker = this.metricWorkers.get(targetKeyArg);
      if (currentWorker === worker) {
        worker.inFlight = false;
      }
    }
  }

  private async resolveContainerId(targetArg: TTelemetryTarget): Promise<string | null> {
    switch (targetArg.type) {
      case 'serviceLogs':
      case 'serviceMetrics': {
        const service = await this.opsServerRef.oneboxRef.database.getServiceByName(targetArg.serviceName);
        return service?.status === 'running' ? service.containerID || null : null;
      }
      case 'platformServiceLogs':
      case 'platformServiceMetrics': {
        const platformService = await this.opsServerRef.oneboxRef.database.getPlatformServiceByType(
          targetArg.serviceType,
        );
        return platformService?.status === 'running' ? platformService.containerId || null : null;
      }
    }
  }

  private parseLogLine(lineArg: string, isErrorArg: boolean): interfaces.data.ILogEntry {
    const timestampMatch = lineArg.match(/^([0-9]{4}-[0-9]{2}-[0-9]{2}T[^\s]+)\s+(.*)$/);
    const timestamp = timestampMatch ? Date.parse(timestampMatch[1]) : Date.now();
    const message = timestampMatch ? timestampMatch[2] : lineArg;
    const messageLower = message.toLowerCase();
    const level = isErrorArg || messageLower.includes('error') || messageLower.includes('fatal')
      ? 'error'
      : messageLower.includes('warn')
        ? 'warn'
        : 'info';
    return {
      serviceId: 0,
      timestamp: Number.isFinite(timestamp) ? timestamp : Date.now(),
      message,
      level,
      source: isErrorArg ? 'stderr' : 'stdout',
    };
  }

  private async pushLog(targetArg: TTelemetryTarget, lineArg: string, isErrorArg: boolean): Promise<void> {
    const targetKey = this.getTargetKey(targetArg);
    const entry = this.parseLogLine(lineArg, isErrorArg);
    await this.pushToSubscribers<interfaces.requests.IReq_PushTelemetryLog>(
      targetKey,
      'pushTelemetryLog',
      { target: targetArg, entry },
    );
  }

  private async pushCurrentMetric(targetArg: TTelemetryTarget): Promise<void> {
    if (targetArg.type !== 'serviceMetrics' && targetArg.type !== 'platformServiceMetrics') return;
    const stats = await this.getCurrentStats(targetArg);
    if (!stats) return;
    await this.pushToSubscribers<interfaces.requests.IReq_PushTelemetryMetric>(
      this.getTargetKey(targetArg),
      'pushTelemetryMetric',
      { target: targetArg, stats, timestamp: Date.now() },
    );
  }

  private enqueueLog(targetArg: TTelemetryTarget, lineArg: string, isErrorArg: boolean): void {
    const targetKey = this.getTargetKey(targetArg);
    let queue = this.logPushQueues.get(targetKey);
    if (!queue) {
      queue = {
        target: targetArg,
        targetKey,
        entries: [],
        flushing: false,
        droppedEntries: 0,
      };
      this.logPushQueues.set(targetKey, queue);
    }
    if (queue.entries.length >= this.maxQueuedLogEntries) {
      queue.entries.shift();
      queue.droppedEntries++;
    }
    queue.entries.push({ line: lineArg, isError: isErrorArg });
    if (!queue.flushing) {
      void this.flushLogQueue(targetKey);
    }
  }

  private async flushLogQueue(targetKeyArg: string): Promise<void> {
    const queue = this.logPushQueues.get(targetKeyArg);
    if (!queue || queue.flushing) return;
    queue.flushing = true;
    try {
      while (queue.entries.length > 0 && this.hasSubscriptionsForTarget(targetKeyArg)) {
        if (queue.droppedEntries > 0) {
          const droppedEntries = queue.droppedEntries;
          queue.droppedEntries = 0;
          await this.pushLog(
            queue.target,
            `[onebox telemetry] dropped ${droppedEntries} log line(s) because the dashboard could not keep up`,
            true,
          );
        }
        const entry = queue.entries.shift();
        if (!entry) continue;
        await this.pushLog(queue.target, entry.line, entry.isError);
      }
    } finally {
      queue.flushing = false;
      if (!this.hasSubscriptionsForTarget(targetKeyArg)) {
        this.logPushQueues.delete(targetKeyArg);
      } else if (queue.entries.length > 0) {
        void this.flushLogQueue(targetKeyArg);
      }
    }
  }

  private async getCurrentStats(targetArg: TTelemetryTarget): Promise<interfaces.data.IContainerStats | null> {
    const containerId = await this.resolveContainerId(targetArg);
    if (!containerId) return null;
    return await this.opsServerRef.oneboxRef.docker.getContainerStats(containerId);
  }

  private async pushToSubscribers<
    TRequest extends interfaces.requests.IReq_PushTelemetryLog | interfaces.requests.IReq_PushTelemetryMetric
  >(
    targetKeyArg: string,
    methodArg: TRequest['method'],
    payloadArg: TRequest['request'],
  ): Promise<void> {
    for (const subscription of this.getSubscriptionsForTarget(targetKeyArg)) {
      try {
        const peer = this.getActivePeerById(subscription.peerId);
        if (!peer) {
          this.removeSubscription(subscription.id);
          continue;
        }
        await this.sendTelemetryPush(methodArg, payloadArg, peer);
      } catch (error) {
        logger.debug(`Telemetry push failed for ${subscription.id}: ${getErrorMessage(error)}`);
        this.removeSubscription(subscription.id);
      }
    }
  }

  private sendTelemetryPush<
    TRequest extends interfaces.requests.IReq_PushTelemetryLog | interfaces.requests.IReq_PushTelemetryMetric
  >(
    methodArg: TRequest['method'],
    payloadArg: TRequest['request'],
    peerArg: ITelemetryPeer,
  ): Promise<void> {
    const previousPush = this.peerPushQueues.get(peerArg.id) || Promise.resolve();
    const queuedPush = previousPush
      .catch(() => undefined)
      .then(() => this.sendTelemetryPushNow(methodArg, payloadArg, peerArg));
    this.peerPushQueues.set(peerArg.id, queuedPush);
    return queuedPush.finally(() => {
      if (this.peerPushQueues.get(peerArg.id) === queuedPush) {
        this.peerPushQueues.delete(peerArg.id);
      }
    });
  }

  private async sendTelemetryPushNow<
    TRequest extends interfaces.requests.IReq_PushTelemetryLog | interfaces.requests.IReq_PushTelemetryMetric
  >(
    methodArg: TRequest['method'],
    payloadArg: TRequest['request'],
    peerArg: ITelemetryPeer,
  ): Promise<void> {
    if (peerArg.readyState !== 1) {
      throw new Error('Telemetry peer is not connected');
    }
    const typedsocket = this.getTypedSocket();
    if (!typedsocket) return;
    const message = {
      method: methodArg,
      request: payloadArg,
      response: null,
      correlation: {
        id: crypto.randomUUID(),
        phase: 'request',
      },
    };
    const interest = await typedsocket.typedrouter.fireEventInterestMap.addInterest(
      message.correlation.id,
      message,
    );
    let timeout: ReturnType<typeof setTimeout> | undefined;
    try {
      peerArg.send(JSON.stringify(message));
      const response = await Promise.race([
        interest.interestFullfilled,
        new Promise<never>((_resolve, reject) => {
          timeout = setTimeout(() => reject(new Error(`Telemetry push timed out after ${this.pushTimeoutMs}ms`)), this.pushTimeoutMs);
        }),
      ]) as { error?: { text?: string } };
      if (response?.error) {
        throw new Error(response.error.text || 'Telemetry push failed');
      }
    } finally {
      if (timeout) {
        clearTimeout(timeout);
      }
      if (!interest.isFullfilled) {
        interest.destroy();
      }
    }
  }

  public async prepareServiceRuntimeReplacement(serviceNameArg: string): Promise<void> {
    this.closeLogWorker(this.getTargetKey({ type: 'serviceLogs', serviceName: serviceNameArg }), false);
  }

  public async refreshServiceTelemetry(serviceNameArg: string): Promise<void> {
    const logTarget: TTelemetryTarget = { type: 'serviceLogs', serviceName: serviceNameArg };
    const logWorker = this.logWorkers.get(this.getTargetKey(logTarget));
    if (logWorker) {
      await this.startLogWorker(logWorker);
    }
    await this.pushCurrentMetric({ type: 'serviceMetrics', serviceName: serviceNameArg });
  }

  public stopServiceTelemetry(serviceNameArg: string): void {
    this.closeLogWorker(this.getTargetKey({ type: 'serviceLogs', serviceName: serviceNameArg }), false);
  }

  public removeServiceTelemetry(serviceNameArg: string): void {
    this.removeTargetTelemetry({ type: 'serviceLogs', serviceName: serviceNameArg });
    this.removeTargetTelemetry({ type: 'serviceMetrics', serviceName: serviceNameArg });
  }

  public async refreshPlatformServiceTelemetry(serviceTypeArg: interfaces.data.TPlatformServiceType): Promise<void> {
    const logTarget: TTelemetryTarget = { type: 'platformServiceLogs', serviceType: serviceTypeArg };
    const logWorker = this.logWorkers.get(this.getTargetKey(logTarget));
    if (logWorker) {
      await this.startLogWorker(logWorker);
    }
    await this.pushCurrentMetric({ type: 'platformServiceMetrics', serviceType: serviceTypeArg });
  }

  public stopPlatformServiceTelemetry(serviceTypeArg: interfaces.data.TPlatformServiceType): void {
    this.closeLogWorker(this.getTargetKey({ type: 'platformServiceLogs', serviceType: serviceTypeArg }), false);
  }

  public removePlatformServiceTelemetry(serviceTypeArg: interfaces.data.TPlatformServiceType): void {
    this.removeTargetTelemetry({ type: 'platformServiceLogs', serviceType: serviceTypeArg });
    this.removeTargetTelemetry({ type: 'platformServiceMetrics', serviceType: serviceTypeArg });
  }

  private removeTargetTelemetry(targetArg: TTelemetryTarget): void {
    const targetKey = this.getTargetKey(targetArg);
    for (const subscription of Array.from(this.subscriptions.values())) {
      if (subscription.targetKey === targetKey) {
        this.subscriptions.delete(subscription.id);
      }
    }
    this.closeLogWorker(targetKey, true);
    this.logPushQueues.delete(targetKey);
    const metricWorker = this.metricWorkers.get(targetKey);
    if (metricWorker) {
      clearInterval(metricWorker.intervalId);
      this.metricWorkers.delete(targetKey);
    }
  }
}
