import * as plugins from './smartvpn.plugins.js';
import type {
  IVpnClientConfig,
  IVpnServerConfig,
} from './smartvpn.interfaces.js';

/**
 * VPN configuration loader, saver, and validator.
 */
export class VpnConfig {
  /**
   * Validate a client config object. Throws on invalid config.
   */
  public static validateClientConfig(config: IVpnClientConfig): void {
    if (config.transport === 'wireguard') {
      // WireGuard-specific validation
      if (!config.wgPrivateKey) {
        throw new Error('VpnConfig: wgPrivateKey is required for WireGuard transport');
      }
      VpnConfig.validateBase64Key(config.wgPrivateKey, 'wgPrivateKey');
      if (!config.wgAddress) {
        throw new Error('VpnConfig: wgAddress is required for WireGuard transport');
      }
      if (!config.serverPublicKey) {
        throw new Error('VpnConfig: serverPublicKey is required for WireGuard transport');
      }
      VpnConfig.validateBase64Key(config.serverPublicKey, 'serverPublicKey');
      if (!config.wgEndpoint) {
        throw new Error('VpnConfig: wgEndpoint is required for WireGuard transport');
      }
      if (config.wgPresharedKey) {
        VpnConfig.validateBase64Key(config.wgPresharedKey, 'wgPresharedKey');
      }
      if (config.wgAllowedIps) {
        for (const cidr of config.wgAllowedIps) {
          if (!VpnConfig.isValidCidr(cidr)) {
            throw new Error(`VpnConfig: invalid allowedIp CIDR: ${cidr}`);
          }
        }
      }
    } else {
      if (!config.serverUrl) {
        throw new Error('VpnConfig: serverUrl is required');
      }
      // For QUIC-only transport, serverUrl is a host:port address; for WebSocket/auto it must be ws:// or wss://
      if (config.transport !== 'quic') {
        if (!config.serverUrl.startsWith('wss://') && !config.serverUrl.startsWith('ws://')) {
          throw new Error('VpnConfig: serverUrl must start with wss:// or ws:// (for WebSocket transport)');
        }
      }
      if (!config.serverPublicKey) {
        throw new Error('VpnConfig: serverPublicKey is required');
      }
      // Noise IK requires client keypair
      if (!config.clientPrivateKey) {
        throw new Error('VpnConfig: clientPrivateKey is required for Noise IK authentication');
      }
      VpnConfig.validateBase64Key(config.clientPrivateKey, 'clientPrivateKey');
      if (!config.clientPublicKey) {
        throw new Error('VpnConfig: clientPublicKey is required for Noise IK authentication');
      }
      VpnConfig.validateBase64Key(config.clientPublicKey, 'clientPublicKey');
    }
    if (config.mtu !== undefined && (config.mtu < 576 || config.mtu > 65535)) {
      throw new Error('VpnConfig: mtu must be between 576 and 65535');
    }
    if (config.keepaliveIntervalSecs !== undefined && config.keepaliveIntervalSecs < 1) {
      throw new Error('VpnConfig: keepaliveIntervalSecs must be >= 1');
    }
    if (config.dns) {
      for (const dns of config.dns) {
        if (!VpnConfig.isValidIp(dns)) {
          throw new Error(`VpnConfig: invalid DNS address: ${dns}`);
        }
      }
    }
  }

  /**
   * Validate a server config object. Throws on invalid config.
   */
  public static validateServerConfig(config: IVpnServerConfig): void {
    if (config.transportMode === 'wireguard') {
      // WireGuard server validation
      if (!config.privateKey) {
        throw new Error('VpnConfig: privateKey is required');
      }
      VpnConfig.validateBase64Key(config.privateKey, 'privateKey');
      if (!config.wgPeers || config.wgPeers.length === 0) {
        throw new Error('VpnConfig: at least one wgPeers entry is required for WireGuard mode');
      }
      for (const peer of config.wgPeers) {
        if (!peer.publicKey) {
          throw new Error('VpnConfig: peer publicKey is required');
        }
        VpnConfig.validateBase64Key(peer.publicKey, 'peer.publicKey');
        if (!peer.allowedIps || peer.allowedIps.length === 0) {
          throw new Error('VpnConfig: peer allowedIps is required');
        }
        for (const cidr of peer.allowedIps) {
          if (!VpnConfig.isValidCidr(cidr)) {
            throw new Error(`VpnConfig: invalid peer allowedIp CIDR: ${cidr}`);
          }
        }
        if (peer.presharedKey) {
          VpnConfig.validateBase64Key(peer.presharedKey, 'peer.presharedKey');
        }
      }
      if (config.wgListenPort !== undefined && (config.wgListenPort < 1 || config.wgListenPort > 65535)) {
        throw new Error('VpnConfig: wgListenPort must be between 1 and 65535');
      }
    } else {
      if (!config.listenAddr) {
        throw new Error('VpnConfig: listenAddr is required');
      }
      if (!config.privateKey) {
        throw new Error('VpnConfig: privateKey is required');
      }
      if (!config.publicKey) {
        throw new Error('VpnConfig: publicKey is required');
      }
      if (!config.subnet) {
        throw new Error('VpnConfig: subnet is required');
      }
      if (!VpnConfig.isValidSubnet(config.subnet)) {
        throw new Error(`VpnConfig: invalid subnet: ${config.subnet}`);
      }
      // Validate client entries if provided
      if (config.clients) {
        for (const client of config.clients) {
          if (!client.clientId) {
            throw new Error('VpnConfig: client entry must have a clientId');
          }
          if (!client.publicKey) {
            throw new Error(`VpnConfig: client '${client.clientId}' must have a publicKey`);
          }
          VpnConfig.validateBase64Key(client.publicKey, `client '${client.clientId}' publicKey`);
        }
      }
    }
    if (config.mtu !== undefined && (config.mtu < 576 || config.mtu > 65535)) {
      throw new Error('VpnConfig: mtu must be between 576 and 65535');
    }
    if (config.keepaliveIntervalSecs !== undefined && config.keepaliveIntervalSecs < 1) {
      throw new Error('VpnConfig: keepaliveIntervalSecs must be >= 1');
    }
  }

  /**
   * Load a config from a JSON file.
   */
  public static async loadFromFile<T>(filePath: string): Promise<T> {
    const content = await plugins.fs.promises.readFile(filePath, 'utf-8');
    return JSON.parse(content) as T;
  }

  /**
   * Save a config to a JSON file.
   */
  public static async saveToFile<T>(filePath: string, config: T): Promise<void> {
    const content = JSON.stringify(config, null, 2);
    await plugins.fs.promises.writeFile(filePath, content, 'utf-8');
  }

  /**
   * Basic IP address validation.
   */
  private static isValidIp(ip: string): boolean {
    const parts = ip.split('.');
    if (parts.length !== 4) return false;
    return parts.every((part) => {
      const num = parseInt(part, 10);
      return !isNaN(num) && num >= 0 && num <= 255 && String(num) === part;
    });
  }

  /**
   * Basic subnet validation (CIDR notation).
   */
  private static isValidSubnet(subnet: string): boolean {
    const [ip, prefix] = subnet.split('/');
    if (!ip || !prefix) return false;
    if (!VpnConfig.isValidIp(ip)) return false;
    const prefixNum = parseInt(prefix, 10);
    return !isNaN(prefixNum) && prefixNum >= 0 && prefixNum <= 32;
  }

  /**
   * Validate a CIDR string (IPv4 or IPv6).
   */
  private static isValidCidr(cidr: string): boolean {
    const parts = cidr.split('/');
    if (parts.length !== 2) return false;
    const prefixNum = parseInt(parts[1], 10);
    if (isNaN(prefixNum) || prefixNum < 0) return false;
    // IPv4
    if (VpnConfig.isValidIp(parts[0])) {
      return prefixNum <= 32;
    }
    // IPv6 (basic check)
    if (parts[0].includes(':')) {
      return prefixNum <= 128;
    }
    return false;
  }

  /**
   * Validate a base64-encoded 32-byte key (WireGuard X25519 format).
   */
  private static validateBase64Key(key: string, fieldName: string): void {
    if (key.length !== 44) {
      throw new Error(`VpnConfig: ${fieldName} must be 44 characters (base64 of 32 bytes), got ${key.length}`);
    }
    try {
      const buf = Buffer.from(key, 'base64');
      if (buf.length !== 32) {
        throw new Error(`VpnConfig: ${fieldName} must decode to 32 bytes, got ${buf.length}`);
      }
    } catch (e) {
      if (e instanceof Error && e.message.startsWith('VpnConfig:')) throw e;
      throw new Error(`VpnConfig: ${fieldName} is not valid base64`);
    }
  }
}
