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

export class PlatformHandler {
  public typedrouter = new plugins.typedrequest.TypedRouter();
  private activeLogStreams = new Map<string, { close: () => void }>();
  private initialStreamCheckTimeout: ReturnType<typeof setTimeout> | null = null;
  private streamCheckInterval: ReturnType<typeof setInterval> | null = null;

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

  /**
   * Start streaming logs from all running containers (platform + user services)
   * and push new entries to connected dashboard clients via TypedSocket
   */
  private async startLogStreaming(): Promise<void> {
    const checkAndStream = async () => {
      // Stream platform service containers
      const platformServices = await this.opsServerRef.oneboxRef.database.getAllPlatformServices();
      for (const service of platformServices) {
        if (service.status !== 'running' || !service.containerId) continue;
        const key = `platform:${service.type}`;
        if (this.activeLogStreams.has(key)) continue;

        logger.info(`Starting log stream for platform service: ${service.type}`);

        try {
          const streamHandle = await this.opsServerRef.oneboxRef.docker.streamContainerLogs(
            service.containerId,
            (line: string, isError: boolean) => {
              this.pushPlatformLogToClients(service.type as interfaces.data.TPlatformServiceType, line, isError);
            },
            { onClose: () => this.activeLogStreams.delete(key) },
          );
          this.activeLogStreams.set(key, streamHandle);
        } catch (err) {
          logger.warn(`Log stream failed for ${service.type}: ${(err as Error).message}`);
          this.activeLogStreams.delete(key);
        }
      }

      // Stream user service containers
      const userServices = await this.opsServerRef.oneboxRef.services.listServices();
      for (const service of userServices) {
        if (service.status !== 'running' || !service.containerID) continue;
        const key = `service:${service.name}`;
        if (this.activeLogStreams.has(key)) continue;

        logger.info(`Starting log stream for user service: ${service.name}`);

        try {
          const streamHandle = await this.opsServerRef.oneboxRef.docker.streamContainerLogs(
            service.containerID,
            (line: string, isError: boolean) => {
              this.pushServiceLogToClients(service.name, line, isError);
            },
            { onClose: () => this.activeLogStreams.delete(key) },
          );
          this.activeLogStreams.set(key, streamHandle);
        } catch (err) {
          logger.warn(`Log stream failed for ${service.name}: ${(err as Error).message}`);
          this.activeLogStreams.delete(key);
        }
      }
    };

    // Initial check after a short delay (let services start first)
    this.initialStreamCheckTimeout = setTimeout(() => checkAndStream(), 5000);
    // Re-check periodically for newly started services
    this.streamCheckInterval = setInterval(() => checkAndStream(), 15000);
  }

  public stopLogStreamForService(serviceNameArg: string): void {
    const key = `service:${serviceNameArg}`;
    const streamHandle = this.activeLogStreams.get(key);
    if (!streamHandle) return;
    streamHandle.close();
    this.activeLogStreams.delete(key);
  }

  public stop(): void {
    if (this.initialStreamCheckTimeout !== null) {
      clearTimeout(this.initialStreamCheckTimeout);
      this.initialStreamCheckTimeout = null;
    }
    if (this.streamCheckInterval !== null) {
      clearInterval(this.streamCheckInterval);
      this.streamCheckInterval = null;
    }
    for (const streamHandle of this.activeLogStreams.values()) {
      streamHandle.close();
    }
    this.activeLogStreams.clear();
  }

  private parseLogLine(line: string, isError: boolean): { timestamp: string; level: string; message: string } {
    const tsMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)/);
    const timestamp = tsMatch ? tsMatch[1] : new Date().toISOString();
    const message = tsMatch ? tsMatch[2] : line;
    const msgLower = message.toLowerCase();
    const level = isError || msgLower.includes('error') || msgLower.includes('fatal')
      ? 'error'
      : msgLower.includes('warn')
        ? 'warn'
        : 'info';
    return { timestamp, level, message };
  }

  private pushPlatformLogToClients(
    serviceType: interfaces.data.TPlatformServiceType,
    line: string,
    isError: boolean,
  ): void {
    const entry = this.parseLogLine(line, isError);
    void this.opsServerRef.pushDashboardEvent('pushPlatformServiceLog', { serviceType, entry });
  }

  private pushServiceLogToClients(
    serviceName: string,
    line: string,
    isError: boolean,
  ): void {
    const entry = this.parseLogLine(line, isError);
    void this.opsServerRef.pushDashboardEvent('pushServiceLog', { serviceName, entry });
  }

  private registerHandlers(): void {
    // Get all platform services
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServices>(
        'getPlatformServices',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const platformServices = await this.opsServerRef.oneboxRef.platformServices.getAllPlatformServices();
          const providers = this.opsServerRef.oneboxRef.platformServices.getAllProviders();
          const coreTrafficStatus = providers.some((provider) => provider.type === 'coretraffic')
            ? await this.opsServerRef.oneboxRef.reverseProxy.observeStatus()
            : null;

          const result = providers.map((provider: any) => {
            const service = platformServices.find((s: any) => s.type === provider.type);
            const isCore = 'isCore' in provider && (provider as any).isCore === true;

            let status: string = service?.status || 'not-deployed';
            if (provider.type === 'coretraffic' && coreTrafficStatus) {
              status = coreTrafficStatus.state;
            }

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

          return { platformServices: result as interfaces.data.IPlatformService[] };
        },
      ),
    );

    // Get specific platform service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformService>(
        'getPlatformService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
          if (!provider) {
            throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
          }

          const service = await this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
          const isCore = 'isCore' in provider && (provider as any).isCore === true;

          let rawStatus: string = service?.status || 'not-deployed';
          if (dataArg.serviceType === 'coretraffic') {
            rawStatus = (await this.opsServerRef.oneboxRef.reverseProxy.observeStatus()).state;
          }

          return {
            platformService: {
              type: provider.type,
              displayName: provider.displayName,
              resourceTypes: provider.resourceTypes,
              status: rawStatus as interfaces.data.TPlatformServiceStatus,
              containerId: service?.containerId,
              isCore,
              createdAt: service?.createdAt,
              updatedAt: service?.updatedAt,
            } as interfaces.data.IPlatformService,
          };
        },
      ),
    );

    // Start platform service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StartPlatformService>(
        'startPlatformService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
          if (!provider) {
            throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
          }

          logger.info(`Starting platform service: ${dataArg.serviceType}`);
          const service = await this.opsServerRef.oneboxRef.platformServices.ensureRunning(dataArg.serviceType);
          await this.opsServerRef.telemetryHandler?.refreshPlatformServiceTelemetry?.(dataArg.serviceType);

          return {
            platformService: {
              type: service.type,
              displayName: provider.displayName,
              resourceTypes: provider.resourceTypes,
              status: service.status,
              containerId: service.containerId,
            },
          };
        },
      ),
    );

    // One-time MinIO -> smartstorage migration
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_MigrateS3ToSmartstorage>(
        'migrateS3ToSmartstorage',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          logger.info('Starting MinIO -> smartstorage migration...');
          return await this.opsServerRef.oneboxRef.platformServices.migrateS3ResourcesToSmartstorage();
        },
      ),
    );

    // Stop platform service
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_StopPlatformService>(
        'stopPlatformService',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const provider = this.opsServerRef.oneboxRef.platformServices.getProvider(dataArg.serviceType);
          if (!provider) {
            throw new plugins.typedrequest.TypedResponseError(`Unknown platform service type: ${dataArg.serviceType}`);
          }

          const isCore = 'isCore' in provider && (provider as any).isCore === true;
          if (isCore) {
            throw new plugins.typedrequest.TypedResponseError(
              `${provider.displayName} is a core service and cannot be stopped`,
            );
          }

          logger.info(`Stopping platform service: ${dataArg.serviceType}`);
          await this.opsServerRef.oneboxRef.platformServices.stopPlatformService(dataArg.serviceType);
          this.opsServerRef.telemetryHandler?.stopPlatformServiceTelemetry?.(dataArg.serviceType);

          return {
            platformService: {
              type: dataArg.serviceType,
              displayName: provider.displayName,
              resourceTypes: provider.resourceTypes,
              status: 'stopped' as const,
            },
          };
        },
      ),
    );

    // Get platform service stats
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceStats>(
        'getPlatformServiceStats',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
          if (!service || !service.containerId) {
            throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
          }

          const stats = await this.opsServerRef.oneboxRef.docker.getContainerStats(service.containerId);
          if (!stats) {
            throw new plugins.typedrequest.TypedResponseError('Could not retrieve container stats');
          }

          return { stats };
        },
      ),
    );

    // Get platform service logs
    this.typedrouter.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetPlatformServiceLogs>(
        'getPlatformServiceLogs',
        async (dataArg) => {
          await requireAdminIdentity(this.opsServerRef.adminHandler, dataArg);
          const service = await this.opsServerRef.oneboxRef.database.getPlatformServiceByType(dataArg.serviceType);
          if (!service || !service.containerId) {
            throw new plugins.typedrequest.TypedResponseError('Platform service has no container');
          }

          const tail = dataArg.tail || 100;
          const logEntries = await this.opsServerRef.oneboxRef.docker.getContainerLogEntries(service.containerId, tail);
          const logLines = logEntries.flatMap((entry) => entry.content
            .split('\n')
            .filter((line) => line.trim())
            .map((line) => ({ line, source: entry.source })));

          const logs = logLines.map((logLine, index: number) => {
            // Try to parse Docker timestamp from beginning of line
            const line = logLine.line;
            const tsMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)/);
            const timestamp = tsMatch ? new Date(tsMatch[1]).getTime() : Date.now();
            const message = tsMatch ? tsMatch[2] : line;
            const msgLower = message.toLowerCase();
            const isError = logLine.source === 'stderr' || msgLower.includes('error') || msgLower.includes('fatal');
            const isWarn = msgLower.includes('warn');
            return {
              id: index,
              serviceId: 0,
              timestamp,
              message,
              level: (isError ? 'error' : isWarn ? 'warn' : 'info') as 'info' | 'warn' | 'error' | 'debug',
              source: logLine.source,
            };
          });

          return { logs };
        },
      ),
    );
  }
}
