// connection_provider.dart — 连接状态机（严格三态，状态由内核事件驱动）
//
// 设计约定：
//   - UI 调用 toggle()，控制器内部读取有效节点 + 认证令牌，调用 ConnectApi
//     并启动 VpnBridge 子进程。
//   - 严禁乐观翻转：VpnPhase.on 必须由 bridge.statusStream 确认后才置。
//   - 状态来源：bridge.statusStream（来自内核回调），非 Timer 模拟。
import 'dart:async';

import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../bridge/vpn_bridge.dart';
import '../bridge/vpn_bridge_provider.dart';
import '../l10n/app_text.dart';
import '../services/api_config.dart';
import '../services/connect_api.dart';
import 'app_providers.dart';
import 'auth_provider.dart';
import 'nodes_provider.dart';
import 'settings_provider.dart';

// ── 设备 ID（MVP 常量；后续由 device_info_plus 取真实 ID）──────────

const _kDeviceId = String.fromEnvironment(
  'PANGOLIN_DEVICE_ID',
  defaultValue: 'mac-001',
);

// API base URL 统一用 api_config.dart 的 kApiBaseUrl(单一来源,勿再重复声明)。

// ── 连接阶段枚举 ──────────────────────────────────────────────────

/// 连接阶段。严格三态，与设计稿一致。
enum VpnPhase { off, connecting, on }

// ── 连接状态快照 ──────────────────────────────────────────────────

class ConnectionState {
  const ConnectionState({required this.phase, this.elapsed = Duration.zero, this.error});

  final VpnPhase phase;
  final Duration elapsed;

  /// 连接失败原因(已本地化);null = 无错误。供 UI 提示,不再静默吞掉。
  final String? error;

  ConnectionState copyWith({VpnPhase? phase, Duration? elapsed}) =>
      ConnectionState(phase: phase ?? this.phase, elapsed: elapsed ?? this.elapsed);

  @override
  bool operator ==(Object other) =>
      other is ConnectionState &&
      other.phase == phase &&
      other.elapsed == elapsed &&
      other.error == error;

  @override
  int get hashCode => Object.hash(phase, elapsed, error);
}

// ── 状态机 ───────────────────────────────────────────────────────

class ConnectionController extends StateNotifier<ConnectionState> {
  ConnectionController(this._ref, this._bridge)
      : super(const ConnectionState(phase: VpnPhase.off)) {
    // 订阅桥状态流：状态由内核事件驱动，严禁 UI 乐观翻转。
    _statusSub = _bridge.statusStream.listen(_onKernelStatus);
  }

  final Ref _ref;
  final VpnBridge _bridge;
  StreamSubscription<VpnStatus>? _statusSub;
  Timer? _elapsed;
  ConnectApi? _api;

  // ── 公有 API ───────────────────────────────────────────────────

  /// 用户点击连接键：按当前状态决定动作，握手中忽略（禁乐观）。
  void toggle() {
    switch (state.phase) {
      case VpnPhase.off:
        _connect();
      case VpnPhase.on:
        _disconnect();
      case VpnPhase.connecting:
        break; // 握手进行中，不响应
    }
  }

  /// 节点切换时触发：已连接则重连。
  void onNodeChanged() {
    if (state.phase == VpnPhase.on || state.phase == VpnPhase.connecting) {
      _disconnect().then((_) => _connect());
    }
  }

  // ── 内部 ─────────────────────────────────────────────────────

  Future<void> _connect() async {
    state = const ConnectionState(phase: VpnPhase.connecting);

    final node = _ref.read(effectiveNodeProvider);
    final zh = _ref.read(localeProvider) == AppLang.zh;

    // 节点未就绪(列表加载中/为空):不连接,提示用户。
    if (node.uuid.isEmpty) {
      if (mounted) {
        state = ConnectionState(
          phase: VpnPhase.off,
          error: zh ? '节点尚未就绪,请稍候重试' : 'Nodes not ready, please retry',
        );
      }
      return;
    }

    try {
      final configJson = await _fetchConfigWithRefresh(node.uuid);
      // bridge.start() 不阻塞至连接建立；on 状态由 statusStream 回调驱动。
      await _bridge.start(configJson);
    } on ConnectApiException catch (e) {
      // 把后端/网络错误冒泡到 UI(原静默回 off,用户不知所以)。
      if (mounted) state = ConnectionState(phase: VpnPhase.off, error: zh ? e.messageZh : e.messageEn);
    } catch (e) {
      if (mounted) {
        state = ConnectionState(
          phase: VpnPhase.off,
          error: zh ? '连接失败,请重试' : 'Connection failed, please retry',
        );
      }
    }
  }

  /// 取配置;access token 过期(401)时用 refresh token 续期后**重试一次**。
  /// 续期失败(refresh 也过期 / 被拒)由 authProvider.refresh() 触发登出 → UI 回登录页。
  Future<String> _fetchConfigWithRefresh(String nodeUuid) async {
    Future<String> doFetch() {
      final token = _ref.read(authProvider).accessToken ?? '';
      _api?.dispose();
      _api = ConnectApi(baseUrl: kApiBaseUrl, authToken: token);
      return _api!.fetchConfig(
        nodeId: nodeUuid,
        deviceId: _kDeviceId,
        // smartRoute 偏好 → 国内分流(#5):国内 IP/域名直连,不走隧道。
        splitCN: _ref.read(settingsProvider).smartRoute,
      );
    }

    try {
      return await doFetch();
    } on ConnectApiException catch (e) {
      if (e.statusCode == 401) {
        // token 过期 → 续期后重试一次;续期成功则用新 token 再取。
        final ok = await _ref.read(authProvider.notifier).refresh();
        if (ok) return await doFetch();
      }
      rethrow;
    }
  }

  Future<void> _disconnect() async {
    _stopElapsed();
    try {
      await _bridge.stop();
    } catch (_) {}
    if (mounted) state = const ConnectionState(phase: VpnPhase.off);
  }

  void _onKernelStatus(VpnStatus s) {
    if (!mounted) return;
    switch (s) {
      case VpnStatus.on:
        state = state.copyWith(phase: VpnPhase.on);
        _startElapsed();
      case VpnStatus.connecting:
        state = state.copyWith(phase: VpnPhase.connecting);
        _stopElapsed();
      case VpnStatus.off:
      case VpnStatus.error:
        state = const ConnectionState(phase: VpnPhase.off);
        _stopElapsed();
    }
  }

  void _startElapsed() {
    _elapsed?.cancel();
    _elapsed = Timer.periodic(const Duration(seconds: 1), (_) {
      if (mounted && state.phase == VpnPhase.on) {
        state = state.copyWith(elapsed: state.elapsed + const Duration(seconds: 1));
      }
    });
  }

  void _stopElapsed() {
    _elapsed?.cancel();
    _elapsed = null;
  }

  @override
  void dispose() {
    _statusSub?.cancel();
    _stopElapsed();
    _api?.dispose();
    super.dispose();
  }
}

// ── Provider ──────────────────────────────────────────────────────

final connectionProvider =
    StateNotifierProvider<ConnectionController, ConnectionState>(
  (ref) => ConnectionController(ref, ref.watch(vpnBridgeProvider)),
);

/// 内核实时统计流(上/下行瞬时速率、字节数)。连接页速度行的真实数据源。
final vpnStatsProvider = StreamProvider<VpnStatsEvent>(
  (ref) => ref.watch(vpnBridgeProvider).statsStream,
);
