import * as plugins from '../../plugins.js';
import * as paths from '../../paths.js';
import type { OpsServer } from '../classes.opsserver.js';
import * as interfaces from '../../../ts_interfaces/index.js';
import { requireOpsAuth } from '../helpers/auth.js';

export class ConfigHandler {
  constructor(private opsServerRef: OpsServer) {
    this.registerHandlers();
  }

  private registerHandlers(): void {
    // Config endpoint registers directly on viewRouter (valid identity required via middleware)
    const router = this.opsServerRef.viewRouter;

    // Get Configuration Handler (read-only)
    router.addTypedHandler(
      new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_GetConfiguration>(
        'getConfiguration',
        async (dataArg, toolsArg) => {
          await requireOpsAuth(this.opsServerRef, dataArg, { scope: 'config:read' });
          const config = await this.getConfiguration();
          return {
            config,
            section: dataArg.section,
          };
        }
      )
    );
  }

  private async getConfiguration(): Promise<interfaces.requests.IConfigData> {
    const dcRouter = this.opsServerRef.dcRouterRef;
    const opts = dcRouter.options;
    const resolvedPaths = dcRouter.resolvedPaths;

    // --- System ---
    const storageBackend: 'filesystem' | 'custom' | 'memory' = opts.dbConfig?.mongoDbUrl
      ? 'custom'
      : 'filesystem';

    // Resolve proxy IPs: fall back to SmartProxy's runtime proxyIPs if not in opts
    let proxyIps = opts.proxyIps || [];
    if (proxyIps.length === 0 && dcRouter.smartProxy) {
      const spSettings = (dcRouter.smartProxy as any).settings;
      if (spSettings?.proxyIPs?.length > 0) {
        proxyIps = spSettings.proxyIPs;
      }
    }

    const system: interfaces.requests.IConfigData['system'] = {
      baseDir: resolvedPaths.dcrouterHomeDir,
      dataDir: resolvedPaths.dataDir,
      publicIp: opts.publicIp || dcRouter.detectedPublicIp || null,
      proxyIps,
      uptime: Math.floor(process.uptime()),
      storageBackend,
      storagePath: opts.dbConfig?.storagePath || resolvedPaths.defaultTsmDbPath,
    };

    // --- SmartProxy ---
    let acmeInfo: interfaces.requests.IConfigData['smartProxy']['acme'] = null;
    if (opts.smartProxyConfig?.acme) {
      const acme = opts.smartProxyConfig.acme;
      acmeInfo = {
        enabled: acme.enabled !== false,
        accountEmail: acme.accountEmail || '',
        useProduction: acme.useProduction !== false,
        autoRenew: acme.autoRenew !== false,
        renewThresholdDays: acme.renewThresholdDays || 30,
      };
    }

    let routeCount = 0;
    if (dcRouter.routeConfigManager) {
      try {
        const merged = await dcRouter.routeConfigManager.getMergedRoutes();
        routeCount = merged.routes.length;
      } catch {
        routeCount = opts.smartProxyConfig?.routes?.length || 0;
      }
    } else if (opts.smartProxyConfig?.routes) {
      routeCount = opts.smartProxyConfig.routes.length;
    }

    const smartProxy: interfaces.requests.IConfigData['smartProxy'] = {
      enabled: !!dcRouter.smartProxy,
      routeCount,
      acme: acmeInfo,
    };

    // --- Email ---
    let emailDomains: string[] = [];
    if (dcRouter.emailServer && (dcRouter.emailServer as any).domainRegistry) {
      emailDomains = (dcRouter.emailServer as any).domainRegistry.getAllDomains();
    } else if (opts.emailConfig?.domains) {
      emailDomains = opts.emailConfig.domains.map((d: any) =>
        typeof d === 'string' ? d : d.domain
      );
    }

    let portMapping: Record<string, number> | null = null;
    if (opts.emailPortConfig?.portMapping) {
      portMapping = {};
      for (const [ext, int] of Object.entries(opts.emailPortConfig.portMapping)) {
        portMapping[String(ext)] = int as number;
      }
    }

    const email: interfaces.requests.IConfigData['email'] = {
      enabled: !!dcRouter.emailServer,
      ports: opts.emailConfig?.ports || [],
      portMapping,
      hostname: opts.emailConfig?.hostname || null,
      domains: emailDomains,
      emailRouteCount: opts.emailConfig?.routes?.length || 0,
      receivedEmailsPath: opts.emailPortConfig?.receivedEmailsPath || null,
    };

    // --- DNS ---
    const dnsRecords = (opts.dnsRecords || []).map(r => ({
      name: r.name,
      type: r.type,
      value: r.value,
      ttl: r.ttl,
    }));

    // dnsChallenge: true when at least one DnsProviderDoc exists in the DB
    // (replaces the legacy `dnsChallenge.cloudflareApiKey` constructor field).
    let dnsChallengeEnabled = false;
    try {
      dnsChallengeEnabled = (await dcRouter.dnsManager?.hasAnyManagedDomain()) ?? false;
    } catch {
      dnsChallengeEnabled = false;
    }

    const dns: interfaces.requests.IConfigData['dns'] = {
      enabled: !!dcRouter.dnsServer,
      port: 53,
      nsDomains: opts.dnsNsDomains || [],
      scopes: opts.dnsScopes || [],
      recordCount: dnsRecords.length,
      records: dnsRecords,
      dnsChallenge: dnsChallengeEnabled,
    };

    // --- TLS ---
    let tlsSource: 'acme' | 'static' | 'none' = 'none';
    if (opts.tls?.certPath && opts.tls?.keyPath) {
      tlsSource = 'static';
    } else if (opts.smartProxyConfig?.acme?.enabled !== false && opts.smartProxyConfig?.acme) {
      tlsSource = 'acme';
    }

    const tls: interfaces.requests.IConfigData['tls'] = {
      contactEmail: opts.tls?.contactEmail || opts.smartProxyConfig?.acme?.accountEmail || null,
      domain: opts.tls?.domain || null,
      source: tlsSource,
      certPath: opts.tls?.certPath || null,
      keyPath: opts.tls?.keyPath || null,
    };

    // --- Database ---
    const dbConfig = opts.dbConfig;
    const cache: interfaces.requests.IConfigData['cache'] = {
      enabled: dbConfig?.enabled !== false,
      storagePath: dbConfig?.storagePath || resolvedPaths.defaultTsmDbPath,
      dbName: dbConfig?.dbName || 'dcrouter',
      defaultTTLDays: 30,
      cleanupIntervalHours: dbConfig?.cleanupIntervalHours || 1,
      ttlConfig: {},
    };

    // --- RADIUS ---
    const radiusCfg = opts.radiusConfig;
    const radius: interfaces.requests.IConfigData['radius'] = {
      enabled: !!dcRouter.radiusServer,
      authPort: radiusCfg?.authPort || null,
      acctPort: radiusCfg?.acctPort || null,
      bindAddress: radiusCfg?.bindAddress || null,
      clientCount: radiusCfg?.clients?.length || 0,
      vlanDefaultVlan: radiusCfg?.vlanAssignment?.defaultVlan ?? null,
      vlanAllowUnknownMacs: radiusCfg?.vlanAssignment?.allowUnknownMacs ?? null,
      vlanMappingCount: radiusCfg?.vlanAssignment?.mappings?.length || 0,
    };

    // --- Remote Ingress ---
    const riCfg = opts.remoteIngressConfig;
    const connectedEdgeIps = dcRouter.tunnelManager?.getConnectedEdgeIps() || [];

    // Determine TLS mode: custom certs > ACME from cert store > self-signed fallback
    let tlsMode: 'custom' | 'acme' | 'self-signed' = 'self-signed';
    if (riCfg?.tls?.certPath && riCfg?.tls?.keyPath) {
      tlsMode = 'custom';
    } else if (riCfg?.hubDomain) {
      try {
        const { ProxyCertDoc } = await import('../../db/index.js');
        const stored = await ProxyCertDoc.findByDomain(riCfg.hubDomain);
        if (stored?.publicKey && stored?.privateKey) {
          tlsMode = 'acme';
        }
      } catch { /* no stored cert */ }
    }

    const remoteIngress: interfaces.requests.IConfigData['remoteIngress'] = {
      enabled: !!dcRouter.remoteIngressManager,
      tunnelPort: riCfg?.tunnelPort || null,
      hubDomain: riCfg?.hubDomain || null,
      tlsMode,
      connectedEdgeIps,
      performance: riCfg?.performance,
    };

    return {
      system,
      smartProxy,
      email,
      dns,
      tls,
      cache,
      radius,
      remoteIngress,
    };
  }
}
