import * as plugins from '../plugins.ts';
import { logger } from '../logging.ts';
import type { Onebox } from '../classes/onebox.ts';
import * as interfaces from '../../ts_interfaces/index.ts';
import * as handlers from './handlers/index.ts';
import { files as bundledFiles } from '../../ts_bundled/bundle.ts';

export class OpsServer {
  public oneboxRef: Onebox;
  public typedrouter = new plugins.typedrequest.TypedRouter();
  public server!: plugins.typedserver.utilityservers.UtilityWebsiteServer;

  // Handler instances
  public adminHandler!: handlers.AdminHandler;
  public statusHandler!: handlers.StatusHandler;
  public servicesHandler!: handlers.ServicesHandler;
  public platformHandler!: handlers.PlatformHandler;
  public sslHandler!: handlers.SslHandler;
  public domainsHandler!: handlers.DomainsHandler;
  public dnsHandler!: handlers.DnsHandler;
  public registryHandler!: handlers.RegistryHandler;
  public networkHandler!: handlers.NetworkHandler;
  public backupsHandler!: handlers.BackupsHandler;
  public schedulesHandler!: handlers.SchedulesHandler;
  public settingsHandler!: handlers.SettingsHandler;
  public managedDcRouterHandler!: handlers.ManagedDcRouterHandler;
  public logsHandler!: handlers.LogsHandler;
  public workspaceHandler!: handlers.WorkspaceHandler;
  public appStoreHandler!: handlers.AppStoreHandler;
  public hostedAppHandler!: handlers.HostedAppHandler;

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

  public async start(port = 3000) {
    this.server = new plugins.typedserver.utilityservers.UtilityWebsiteServer({
      domain: 'localhost',
      feedMetadata: undefined,
      bundledContent: bundledFiles,
      addCustomRoutes: async (typedserver) => this.registerCustomRoutes(typedserver),
    });

    // Chain typedrouters: server -> opsServer -> individual handlers
    this.server.typedrouter.addTypedRouter(this.typedrouter);

    // Set up all handlers
    await this.setupHandlers();

    await this.server.start(port);
    logger.success(`OpsServer started on http://localhost:${port}`);
  }

  private async setupHandlers(): Promise<void> {
    // AdminHandler requires async initialization for JWT key generation
    this.adminHandler = new handlers.AdminHandler(this);
    await this.adminHandler.initialize();

    // All other handlers self-register in their constructors
    this.statusHandler = new handlers.StatusHandler(this);
    this.servicesHandler = new handlers.ServicesHandler(this);
    this.platformHandler = new handlers.PlatformHandler(this);
    this.sslHandler = new handlers.SslHandler(this);
    this.domainsHandler = new handlers.DomainsHandler(this);
    this.dnsHandler = new handlers.DnsHandler(this);
    this.registryHandler = new handlers.RegistryHandler(this);
    this.networkHandler = new handlers.NetworkHandler(this);
    this.backupsHandler = new handlers.BackupsHandler(this);
    this.schedulesHandler = new handlers.SchedulesHandler(this);
    this.settingsHandler = new handlers.SettingsHandler(this);
    this.managedDcRouterHandler = new handlers.ManagedDcRouterHandler(this);
    this.logsHandler = new handlers.LogsHandler(this);
    this.workspaceHandler = new handlers.WorkspaceHandler(this);
    this.appStoreHandler = new handlers.AppStoreHandler(this);
    this.hostedAppHandler = new handlers.HostedAppHandler(this);

    logger.success('OpsServer TypedRequest handlers initialized');
  }

  private registerCustomRoutes(typedserver: plugins.typedserver.TypedServer): void {
    typedserver.addRoute(
      '/v2',
      'ALL',
      async (ctx) => this.oneboxRef.registry.handleRequest(ctx.request),
    );
    typedserver.addRoute(
      '/v2/*',
      'ALL',
      async (ctx) => this.oneboxRef.registry.handleRequest(ctx.request),
    );
    typedserver.addRoute(
      '/backups/:backupId/download',
      'GET',
      async (ctx) => {
        const jwt = ctx.query.jwt;
        if (!jwt) {
          return new Response('Missing JWT', { status: 401 });
        }

        try {
          await this.adminHandler.getVerifiedAdminIdentity({
            jwt,
            userId: '',
            username: '',
            expiresAt: 0,
            role: 'user',
          });
        } catch {
          return new Response('Unauthorized', { status: 401 });
        }

        const backupId = Number(ctx.params.backupId);
        if (!Number.isInteger(backupId) || backupId < 1) {
          return new Response('Invalid backup id', { status: 400 });
        }

        const backup = this.oneboxRef.database.getBackupById(backupId);
        if (!backup) {
          return new Response('Backup not found', { status: 404 });
        }

        const filename = this.sanitizeDownloadFilename(
          backup.filename || `${backup.serviceName}-${backup.createdAt}.tar.enc`,
        );

        let filePath = this.oneboxRef.backupManager.getBackupFilePath(backupId);
        let shouldCleanup = false;

        if (!filePath) {
          filePath = await this.oneboxRef.backupManager.getBackupExportPath(backupId);
          shouldCleanup = !!filePath;
        }

        if (!filePath) {
          return new Response('Backup export unavailable', { status: 404 });
        }

        try {
          const fileData = await Deno.readFile(filePath);
          return new Response(fileData, {
            status: 200,
            headers: {
              'content-type': 'application/octet-stream',
              'content-disposition': `attachment; filename="${filename}"`,
              'content-length': String(fileData.byteLength),
              'cache-control': 'no-store',
            },
          });
        } finally {
          if (shouldCleanup) {
            await Deno.remove(filePath).catch(() => {});
          }
        }
      },
    );
  }

  private sanitizeDownloadFilename(filename: string): string {
    return filename.replace(/["\\\r\n]/g, '_');
  }

  public async stop() {
    if (this.server) {
      await this.server.stop();
      logger.success('OpsServer stopped');
    }
  }

  public async pushDashboardEvent(method: string, payload: unknown): Promise<void> {
    const typedsocket = (this.server as any)?.typedserver?.typedsocket;
    if (!typedsocket) {
      return;
    }

    const connections = await typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard');
    await Promise.allSettled(
      connections.map((connection: any) => typedsocket.createTypedRequest(method, connection).fire(payload)),
    );
  }

  public async broadcastServiceUpdate(
    serviceName: string,
    action: interfaces.requests.IReq_PushServiceUpdate['request']['action'],
    service?: interfaces.data.IService | null,
  ): Promise<void> {
    await this.pushDashboardEvent('pushServiceUpdate', {
      action,
      serviceName,
      service: service || undefined,
    });
  }
}
