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';

    const proxyIps = opts.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 ---
    const acmeConfig = dcRouter.acmeConfigManager?.getConfig();
    let acmeInfo: interfaces.requests.IConfigData['smartProxy']['acme'] = null;
    if (acmeConfig) {
      acmeInfo = {
        enabled: acmeConfig.enabled,
        accountEmail: acmeConfig.accountEmail,
        useProduction: acmeConfig.useProduction,
        autoRenew: acmeConfig.autoRenew,
        renewThresholdDays: acmeConfig.renewThresholdDays,
      };
    }

    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;
    const emailSettings = dcRouter.emailSettingsManager?.getPublicSettings();
    const rawPortMapping = emailSettings?.portMapping || opts.emailPortConfig?.portMapping;
    if (rawPortMapping) {
      portMapping = {};
      for (const [ext, int] of Object.entries(rawPortMapping)) {
        portMapping[String(ext)] = int as number;
      }
    }

    const email: interfaces.requests.IConfigData['email'] = {
      enabled: emailSettings?.enabled ?? !!dcRouter.emailServer,
      ports: emailSettings?.ports || opts.emailConfig?.ports || [],
      portMapping,
      hostname: emailSettings?.hostname || opts.emailConfig?.hostname || null,
      domains: emailDomains,
      emailRouteCount: emailSettings?.routeCount ?? opts.emailConfig?.routes?.length ?? 0,
      receivedEmailsPath: emailSettings?.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.
    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 (acmeConfig?.enabled) {
      tlsSource = 'acme';
    }

    const tls: interfaces.requests.IConfigData['tls'] = {
      contactEmail: acmeConfig?.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 riSettings = dcRouter.remoteIngressManager?.getHubSettings();
    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 (riSettings?.hubDomain) {
      try {
        const { ProxyCertDoc } = await import('../../db/index.js');
        const stored = await ProxyCertDoc.findByDomain(riSettings.hubDomain);
        if (stored?.publicKey && stored?.privateKey) {
          tlsMode = 'acme';
        }
      } catch { /* no stored cert */ }
    }

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

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