// desktop_vpn_bridge.dart — 桌面端 VpnBridge 实现（tsk_SLCsjNgtmng3）
//
// 实现 VpnBridge 接口，底层驱动 KernelProcess（默认使用 DesktopKernelProcess）。
//
// 关键职责：
//   1. start(configJson):
//        - 注入 experimental.clash_api（随机高位端口 + 随机 secret）
//        - 写 config 到 <AppSupport>/pangolin/kernel/config_<ts>.json（权限 0600）
//        - 调 kernel.spawn(configPath)
//   2. stop(): kernel.kill() + 取消自动重连
//   3. statusStream / statsStream: 代理 KernelProcess 事件流
//   4. selectOutbound: 通过 Clash API PUT /proxies/{group} 切换出口（M4）
//   5. setKillSwitch: 修改 TUN strict_route 并重载内核（M5）
//   6. 自动退避重连: 内核崩溃后 1s/2s/4s/8s/16s/30s 上限自动重试（M5）
//
// macOS PoC 依赖:
//   · sing-box 需能建立 TUN 接口（sudo 或 sudoers 免密白名单）
//   · 环境变量 PANGOLIN_SINGBOX_BIN 可指定二进制路径
//   · 实际 REALITY 参数由调用方（PoC 脚本）以静态 config 传入
//
// ignore_for_file: avoid_print

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'kernel_process.dart';
import 'log.dart';
import 'vpn_bridge.dart';

/// 桌面端 VPN 桥接实现（macOS / Linux / Windows PoC）。
///
/// 使用示例（PoC 命令行测试）：
/// ```dart
/// final bridge = DesktopVpnBridge();
/// bridge.statusStream.listen(print);
/// bridge.statsStream.listen(print);
/// await bridge.start(jsonEncode(myConfig));
/// // ...
/// await bridge.stop();
/// bridge.dispose();
/// ```
class DesktopVpnBridge implements VpnBridge {
  /// 构造函数。
  ///
  /// [kernel] 可注入假实现，用于测试。默认创建 [DesktopKernelProcess]。
  /// [configDirOverride] 仅供测试使用，覆盖配置写入目录（否则写到应用支持目录）。
  DesktopVpnBridge({
    KernelProcess? kernel,
    bool useSudo = true,
    String? configDirOverride,
  })  : _kernel = kernel ??
            DesktopKernelProcess(
              readyTimeout: const Duration(seconds: 20),
              statsPollInterval: const Duration(seconds: 1),
              useSudo: useSudo,
            ),
        _configDirOverride = configDirOverride;

  final KernelProcess _kernel;
  final String? _configDirOverride;

  // ── M5: Kill-switch 状态 ──────────────────────────────────────
  bool _killSwitchEnabled = false;

  // ── M5: 自动退避重连状态 ─────────────────────────────────────
  // 记录用户最初传入的 config（未注入 Clash API 之前的原始字符串），
  // 重连时重用，保持每次重连都有新 port+secret。
  String? _lastUserConfigJson;
  bool _shouldAutoReconnect = false;
  int _reconnectAttempt = 0;
  Timer? _reconnectTimer;
  StreamSubscription<VpnStatus>? _kernelStatusSub;

  // ── 退避延迟序列（秒）：1/2/4/8/16/30/30/…
  static const _kRetryDelaysSec = [1, 2, 4, 8, 16, 30];

  // ── VpnBridge: start ─────────────────────────────────────────

  @override
  Future<void> start(String configJson) async {
    // 保存原始 config（未注入 Clash API），用于重连
    _lastUserConfigJson = configJson;
    _shouldAutoReconnect = true;
    // 注意：不在此重置 _reconnectAttempt，由 stop() 和重连成功回调负责重置，
    // 确保连续 start 失败时退避延迟单调递增。

    // 订阅内核状态流：内核崩溃时自动触发退避重连
    _kernelStatusSub?.cancel();
    _kernelStatusSub = _kernel.statusStream.listen((status) {
      if (status == VpnStatus.error && _shouldAutoReconnect) {
        _scheduleReconnect();
      }
    });

    // 1. 将 kill-switch 偏好写入 config（strict_route 字段）
    final configWithKs = applyKillSwitchToConfig(configJson, _killSwitchEnabled);

    // 2. 注入 Clash API 配置（随机端口 + secret）
    final (enrichedJson, port, secret) = injectClashApi(configWithKs);

    // 3. 写 config 到应用支持目录（0600 权限）
    final configPath = await writeConfig(enrichedJson);
    logLine('DesktopVpnBridge', 'config written: $configPath');
    logLine('DesktopVpnBridge', 'clash_api port=$port secret_len=${secret.length}');
    logLine('DesktopVpnBridge', 'killSwitch=$_killSwitchEnabled');

    // 4. 启动内核子进程（blocking until Clash API ready or error）
    await _kernel.spawn(configPath);
  }

  // ── VpnBridge: stop ──────────────────────────────────────────

  @override
  Future<void> stop() async {
    // 取消自动重连，防止 kill 后再次被重连定时器触发
    _shouldAutoReconnect = false;
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    _kernelStatusSub?.cancel();
    _kernelStatusSub = null;
    _reconnectAttempt = 0;
    await _kernel.kill(gracePeriod: const Duration(seconds: 5));
  }

  // ── VpnBridge: getStatus ─────────────────────────────────────

  @override
  Future<VpnStatus> getStatus() async {
    return _kernel.isRunning ? VpnStatus.on : VpnStatus.off;
  }

  // ── VpnBridge: selectOutbound ─────────────────────────────────

  @override
  Future<void> selectOutbound(String tag) async {
    if (!_kernel.isRunning) {
      throw StateError('kernel not running; cannot selectOutbound');
    }
    // sing-box Selector 出口组名固定为 "proxy"（见 config 模板 outbounds[].tag）。
    // tag 可以是单节点 tag（如 "reality-out"）或 urltest 组 tag（如 "auto-select"）。
    try {
      await _kernel.clashApiClient.selectProxy('proxy', tag);
    } catch (e) {
      logLine('DesktopVpnBridge', 'selectOutbound(tag=$tag) error: $e');
      rethrow;
    }
  }

  // ── VpnBridge: getActiveOutbound ─────────────────────────────

  @override
  Future<String> getActiveOutbound() async {
    if (!_kernel.isRunning) return 'auto';
    try {
      final data = await _kernel.clashApiClient.getProxies();
      final proxies = data['proxies'];
      if (proxies is Map) {
        final proxy = proxies['proxy'];
        if (proxy is Map) {
          return (proxy['now'] as String?) ?? 'auto';
        }
      }
    } catch (_) {}
    return 'auto';
  }

  // ── VpnBridge: setKillSwitch ─────────────────────────────────

  /// 设置 Kill-switch 开关。
  ///
  /// Kill-switch 通过修改 TUN inbound 的 `strict_route` 字段实现：
  ///   - `on=true`:  `strict_route: true`  → 内核停止时流量被系统丢弃
  ///   - `on=false`: `strict_route: false` → 内核停止时流量走正常路由
  ///
  /// 若内核当前正在运行，会触发一次静默重载（先 kill，再以新 config 重启）。
  @override
  Future<void> setKillSwitch({required bool on}) async {
    if (_killSwitchEnabled == on) return;
    _killSwitchEnabled = on;
    logLine('DesktopVpnBridge', 'setKillSwitch=$on');

    // 若内核正在运行，用新设置重载（会有约 1~2s 重连）
    if (_kernel.isRunning && _lastUserConfigJson != null) {
      logLine('DesktopVpnBridge', 'reloading kernel for killSwitch change');
      // 暂停自动重连，避免 kill 触发重连定时器
      final wasAutoReconnect = _shouldAutoReconnect;
      _shouldAutoReconnect = false;
      _reconnectTimer?.cancel();
      _kernelStatusSub?.cancel();
      _kernelStatusSub = null;
      await _kernel.kill(gracePeriod: const Duration(seconds: 3));
      _shouldAutoReconnect = wasAutoReconnect;
      // start() 会重新订阅 _kernelStatusSub
      await start(_lastUserConfigJson!);
    }
  }

  // ── VpnBridge: 事件流 ────────────────────────────────────────

  @override
  Stream<VpnStatus> get statusStream => _kernel.statusStream;

  @override
  Stream<VpnStatsEvent> get statsStream => _kernel.statsStream;

  // ── VpnBridge: dispose ───────────────────────────────────────

  @override
  void dispose() {
    _shouldAutoReconnect = false;
    _reconnectTimer?.cancel();
    _reconnectTimer = null;
    _kernelStatusSub?.cancel();
    _kernelStatusSub = null;
    if (_kernel is DesktopKernelProcess) {
      (_kernel as DesktopKernelProcess).dispose();
    }
  }

  // ── M5: 退避重连调度 ─────────────────────────────────────────

  /// 安排下一次退避重连。
  /// 延迟序列：1s → 2s → 4s → 8s → 16s → 30s → 30s → …
  void _scheduleReconnect() {
    if (!_shouldAutoReconnect || _lastUserConfigJson == null) return;
    _reconnectTimer?.cancel();

    final delaySec = _kRetryDelaysSec[
        _reconnectAttempt.clamp(0, _kRetryDelaysSec.length - 1)];
    _reconnectAttempt++;

    logLine('DesktopVpnBridge',
        'auto-reconnect in ${delaySec}s (attempt $_reconnectAttempt)');

    _reconnectTimer = Timer(Duration(seconds: delaySec), () async {
      if (!_shouldAutoReconnect || _lastUserConfigJson == null) return;
      try {
        logLine('DesktopVpnBridge', 'auto-reconnect: attempting start...');
        await start(_lastUserConfigJson!);
        _reconnectAttempt = 0; // 成功后重置退避计数
        logLine('DesktopVpnBridge', 'auto-reconnect: success');
      } catch (e) {
        logLine('DesktopVpnBridge', 'auto-reconnect: start failed: $e');
        // start() 失败（内核未能启动）→ 手动调度下一次重试
        if (_shouldAutoReconnect) _scheduleReconnect();
      }
    });
  }

  // ── 内部: Clash API 注入（@visibleForTesting）─────────────────

  /// 检查 configJson 中是否有 experimental.clash_api；若无则注入随机端口+secret。
  /// 返回 (修改后 JSON, 端口, secret)。
  // @visibleForTesting
  static (String, int, String) injectClashApi(String configJson) {
    late Map<String, dynamic> cfg;
    try {
      cfg = jsonDecode(configJson) as Map<String, dynamic>;
    } catch (e) {
      throw FormatException('invalid configJson: $e');
    }

    // 若已有 clash_api，尊重现有值
    final rawExp = cfg['experimental'];
    final experimental = (rawExp is Map)
        ? rawExp.cast<String, dynamic>()
        : <String, dynamic>{};

    if (experimental.containsKey('clash_api')) {
      final api =
          (experimental['clash_api'] as Map).cast<String, dynamic>();
      final ctrl =
          (api['external_controller'] as String?) ?? '127.0.0.1:9090';
      final secret = (api['secret'] as String?) ?? '';
      final port = int.tryParse(ctrl.split(':').last) ?? 9090;
      return (configJson, port, secret);
    }

    // 注入随机 port + secret
    final port = generateClashApiPort();
    final secret = generateClashApiSecret();
    final updated = {
      ...cfg,
      'experimental': {
        ...experimental,
        'clash_api': {
          'external_controller': '127.0.0.1:$port',
          'secret': secret,
        },
      },
    };
    return (jsonEncode(updated), port, secret);
  }

  // ── 内部: Kill-switch 注入（@visibleForTesting）──────────────

  /// 将 kill-switch 偏好写入 configJson 的 TUN inbound `strict_route` 字段。
  ///
  /// - `killSwitch=true`:  `strict_route: true`（所有流量绑定 TUN，内核停止即断流）
  /// - `killSwitch=false`: `strict_route: false`（内核停止后流量走默认路由）
  ///
  /// 若配置中没有 TUN inbound，原样返回（不修改）。
  // @visibleForTesting
  static String applyKillSwitchToConfig(String configJson, bool killSwitch) {
    late Map<String, dynamic> cfg;
    try {
      cfg = jsonDecode(configJson) as Map<String, dynamic>;
    } catch (_) {
      return configJson; // 解析失败，原样返回
    }

    final inbounds = cfg['inbounds'];
    if (inbounds is! List) return configJson;

    bool changed = false;
    final updatedInbounds = inbounds.map((ib) {
      if (ib is Map && ib['type'] == 'tun') {
        changed = true;
        return <String, dynamic>{...Map<String, dynamic>.from(ib), 'strict_route': killSwitch};
      }
      return ib;
    }).toList();

    if (!changed) return configJson;
    return jsonEncode(<String, dynamic>{...cfg, 'inbounds': updatedInbounds});
  }

  // ── 内部: 配置文件写入 ────────────────────────────────────────

  // @visibleForTesting
  Future<String> writeConfig(String configJson) async {
    final dir = await _resolveConfigDir();
    await dir.create(recursive: true);

    final ts = DateTime.now().millisecondsSinceEpoch;
    final file = File('${dir.path}/config_$ts.json');
    await file.writeAsString(configJson, flush: true);

    // 0600 权限（仅当前用户可读写）
    if (!Platform.isWindows) {
      try {
        await Process.run('chmod', ['600', file.path]);
      } catch (_) {
        logLine('DesktopVpnBridge', 'warning: chmod 600 failed for ${file.path}');
      }
    }
    return file.path;
  }

  Future<Directory> _resolveConfigDir() async {
    if (_configDirOverride != null) {
      return Directory(_configDirOverride!);
    }
    return _defaultConfigDir();
  }

  static Future<Directory> _defaultConfigDir() async {
    if (Platform.isMacOS) {
      final home = Platform.environment['HOME'] ?? '/tmp';
      return Directory(
          '$home/Library/Application Support/com.pangolin.vpn/kernel');
    }
    if (Platform.isLinux) {
      final base = Platform.environment['XDG_DATA_HOME'] ??
          '${Platform.environment['HOME'] ?? '/tmp'}/.local/share';
      return Directory('$base/pangolin-vpn/kernel');
    }
    if (Platform.isWindows) {
      final appData =
          Platform.environment['LOCALAPPDATA'] ?? r'C:\ProgramData';
      return Directory('$appData\\Pangolin\\kernel');
    }
    return Directory('/tmp/pangolin/kernel');
  }
}
