import 'dart:async';
import 'dart:developer' as developer;

import 'package:dio/dio.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:shared_preferences/shared_preferences.dart';

import '../core/config/app_config.dart';
import '../data/models/node/service_node.dart';
import 'app_provider.dart';

// ── State ─────────────────────────────────────────────────

class NodeState {
  final List<ServiceNode> nodes;
  final ServiceNode? currentNode;
  final bool isLoading;
  final bool isSpeedTesting;

  /// nodeId → latency(ms)。null 表示测速中或超时
  final Map<int, int?> latencyMap;

  const NodeState({
    this.nodes = const [],
    this.currentNode,
    this.isLoading = false,
    this.isSpeedTesting = false,
    this.latencyMap = const {},
  });

  NodeState copyWith({
    List<ServiceNode>? nodes,
    ServiceNode? currentNode,
    bool? isLoading,
    bool? isSpeedTesting,
    Map<int, int?>? latencyMap,
    bool clearCurrentNode = false,
  }) {
    return NodeState(
      nodes: nodes ?? this.nodes,
      currentNode: clearCurrentNode ? null : (currentNode ?? this.currentNode),
      isLoading: isLoading ?? this.isLoading,
      isSpeedTesting: isSpeedTesting ?? this.isSpeedTesting,
      latencyMap: latencyMap ?? this.latencyMap,
    );
  }
}

// ── Notifier ──────────────────────────────────────────────

class NodeNotifier extends Notifier<NodeState> {
  // 熔断内部状态（不暴露给 UI）
  final Map<int, int> _failCounts = {};
  final Map<int, DateTime> _circuitOpenTimes = {};

  @override
  NodeState build() {
    Future.microtask(_init);
    return const NodeState(isLoading: true);
  }

  SharedPreferences get _prefs => ref.read(sharedPreferencesProvider);

  /// Debug / Release 分 key 缓存，避免 debug 构建复用 release 写入的生产节点缓存
  String get _nodeListCacheKey =>
      AppConfig.isDebug ? 'node_list_cache_debug' : 'node_list_cache';
  String get _selectedNodeIdKey =>
      AppConfig.isDebug ? 'node_selected_id_debug' : 'node_selected_id';

  // ── 初始化 ────────────────────────────────────────────

  Future<void> _init() async {
    // 1. 加载本地缓存；若缓存为空，用编译期 fallbackHosts 构造虚拟节点垫底，
    //    避免首屏在拉到节点列表前出现"无节点可切"的窗口
    final cachedNodes = _loadCachedNodes();
    final savedId = _prefs.getInt(_selectedNodeIdKey);

    final initialNodes =
        cachedNodes.isNotEmpty ? cachedNodes : _buildFallbackNodes();

    ServiceNode? selected;
    if (savedId != null) {
      selected = initialNodes.where((n) => n.id == savedId).firstOrNull;
    }
    selected ??= initialNodes.firstOrNull;

    state = state.copyWith(
      nodes: initialNodes,
      currentNode: selected,
      isLoading: false,
    );

    // 1.5 主动探活：缓存的 currentNode 可能已下线，先 ping 一次。
    //     失败立即 failover，避免首屏业务请求承担探活成本（用户看到红 toast）。
    //     不 await——与 fetchNodeList/测速 并行，最终都会收敛到正确节点。
    unawaited(_probeCurrentNode());

    // 2. 从远程 CDN 拉取最新兜底列表，合并到 fallbackHosts 头部
    final remoteFallbacks = await _fetchRemoteFallbackHosts();

    // 3. 后台拉取最新节点列表（优先使用远程兜底 + 本地硬编码兜底）
    await fetchNodeList(extraHosts: remoteFallbacks);

    // 4. 测速
    if (state.nodes.isNotEmpty) {
      await speedTestAll();
    }
  }

  /// 主动探活当前节点：用一次轻量 GET 请求验证 currentNode 是否可达，
  /// 失败立即触发 immediate failover 切下一个候选。
  /// 与 _init 中的 fetchNodeList / speedTestAll 并发执行，竞争状态下最后写入者胜。
  Future<void> _probeCurrentNode() async {
    final node = state.currentNode;
    if (node == null) return;

    final dio = Dio(BaseOptions(
      connectTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
      receiveTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
    ));

    try {
      await dio.get('${node.normalizedApiHost}contract/watchlist/test');
      developer.log('Node probe ok: ${node.name}', name: 'Node');
    } catch (e) {
      developer.log(
        'Node probe failed: ${node.name} → $e, switching node',
        name: 'Node',
      );
      reportFailure(immediate: true);
    }
  }

  /// 用 AppConfig.fallbackHosts 构造一组兜底虚拟节点，id 用负数与真节点隔离。
  /// 仅在没有缓存节点 / 节点列表 API 不可达时作为应急节点池使用。
  List<ServiceNode> _buildFallbackNodes() {
    final hosts = AppConfig.fallbackHosts;
    return List<ServiceNode>.generate(hosts.length, (i) {
      final api = hosts[i];
      var ws = api.replaceFirst(RegExp(r'^https?://'), 'wss://');
      if (ws.endsWith('/')) ws = ws.substring(0, ws.length - 1);
      ws = '$ws/market-new';
      return ServiceNode(
        id: -(i + 1),
        name: 'Fallback ${i + 1}',
        apiHost: api,
        wsHost: ws,
        status: 1,
      );
    });
  }

  /// 从远程 CDN 拉取兜底 Host 列表，失败时静默返回空列表
  Future<List<String>> _fetchRemoteFallbackHosts() async {
    final dio = Dio(BaseOptions(
      connectTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
      receiveTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
    ));

    for (final url in AppConfig.remoteConfigUrls) {
      try {
        final response = await dio.get<Map<String, dynamic>>(url);
        final hosts = (response.data?['fallbackHosts'] as List<dynamic>?)
            ?.whereType<String>()
            .where((h) => h.startsWith('http'))
            .toList();
        if (hosts != null && hosts.isNotEmpty) {
          developer.log('Remote config loaded from $url: ${hosts.length} hosts',
              name: 'Node');
          return hosts;
        }
      } catch (e) {
        developer.log('Remote config fetch failed from $url: $e', name: 'Node');
      }
    }
    return [];
  }

  List<ServiceNode> _loadCachedNodes() {
    final json = _prefs.getString(_nodeListCacheKey);
    if (json == null || json.isEmpty) return [];
    try {
      return ServiceNode.listFromJson(json);
    } catch (_) {
      return [];
    }
  }

  void _saveCachedNodes(List<ServiceNode> nodes) {
    _prefs.setString(_nodeListCacheKey, ServiceNode.listToJson(nodes));
  }

  // ── 拉取节点列表 ─────────────────────────────────────

  Future<void> fetchNodeList({List<String> extraHosts = const []}) async {
    final dio = Dio(BaseOptions(
      connectTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
      receiveTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
      headers: {'Content-Type': 'application/json'},
    ));

    // 远程兜底列表优先，再用硬编码列表；去重保留顺序
    final seen = <String>{};
    final hostsToTry = [...extraHosts, ...AppConfig.fallbackHosts]
        .where((h) => seen.add(h))
        .toList();

    for (final host in hostsToTry) {
      try {
        final response =
            await dio.get<Map<String, dynamic>>('${host}contract/node/list');
        final body = response.data;
        if (body == null) continue;
        final code = body['code'] as int? ?? -1;
        if (code != 0) continue;

        final list = (body['data'] as List<dynamic>)
            .map((e) => ServiceNode.fromJson(e as Map<String, dynamic>))
            .where((n) => n.isValid)
            .toList();

        if (list.isEmpty) continue;

        // 更新缓存
        _saveCachedNodes(list);

        // 保持当前选择（如果仍在新列表中）
        final currentId = state.currentNode?.id;
        ServiceNode? selected;
        if (currentId != null) {
          selected = list.where((n) => n.id == currentId).firstOrNull;
        }
        selected ??= list.first;

        state = state.copyWith(
          nodes: list,
          currentNode: selected,
          isLoading: false,
        );
        return;
      } catch (e) {
        developer.log('fetchNodeList from $host failed: $e', name: 'Node');
        continue;
      }
    }

    // 全部失败，保持当前状态
    state = state.copyWith(isLoading: false);
  }

  // ── 测速 ──────────────────────────────────────────────

  Future<void> speedTestAll() async {
    if (state.nodes.isEmpty) return;

    state = state.copyWith(isSpeedTesting: true, latencyMap: {});

    final results = <int, int?>{};
    final futures = <Future<void>>[];

    for (final node in state.nodes) {
      futures.add(
        _speedTest(node).then((ms) => results[node.id] = ms),
      );
    }

    await Future.wait(futures);

    state = state.copyWith(
      isSpeedTesting: false,
      latencyMap: results,
    );

    // 如果没有用户手动选择，自动切到最快节点
    final savedId = _prefs.getInt(_selectedNodeIdKey);
    if (savedId == null) {
      _autoSelectBestNode(results);
    }
  }

  Future<int?> _speedTest(ServiceNode node) async {
    final dio = Dio(BaseOptions(
      connectTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
      receiveTimeout:
          const Duration(milliseconds: AppConfig.speedTestTimeoutMs),
    ));

    final sw = Stopwatch()..start();
    try {
      await dio.get('${node.normalizedApiHost}contract/watchlist/test');
      sw.stop();
      return sw.elapsedMilliseconds;
    } catch (_) {
      sw.stop();
      return null; // 超时或不可达
    }
  }

  void _autoSelectBestNode(Map<int, int?> latencyMap) {
    final reachable = state.nodes.where((n) {
      final ms = latencyMap[n.id];
      return ms != null;
    }).toList();

    if (reachable.isEmpty) return;

    reachable.sort((a, b) {
      final la = latencyMap[a.id]!;
      final lb = latencyMap[b.id]!;
      return la.compareTo(lb);
    });

    final best = reachable.first;
    if (best.id != state.currentNode?.id) {
      state = state.copyWith(currentNode: best);
    }
  }

  // ── 用户手动选择 ─────────────────────────────────────

  void selectNode(ServiceNode node) {
    _prefs.setInt(_selectedNodeIdKey, node.id);
    _failCounts.remove(node.id);
    state = state.copyWith(currentNode: node);
  }

  // ── 故障转移（由 Dio 拦截器调用）────────────────────

  /// 报告请求失败，返回 true 表示已切换节点。
  ///
  /// [immediate] 为 true 时第一次失败就熔断当前节点切下一个；
  /// 用于 DNS / connect / timeout 等"硬"网络错误，避免连续等 N 次超时才切。
  /// 5xx 等服务端错误仍走累积阈值，避免单点抖动导致频繁切换。
  bool reportFailure({bool immediate = false}) {
    final node = state.currentNode;
    if (node == null || state.nodes.length <= 1) return false;

    final count = (_failCounts[node.id] ?? 0) + 1;
    _failCounts[node.id] = count;

    final shouldTrip = immediate || count >= AppConfig.failoverThreshold;
    if (shouldTrip) {
      // 熔断当前节点
      _circuitOpenTimes[node.id] = DateTime.now();

      final next = _getNextAvailableNode();
      if (next != null) {
        developer.log(
          'Failover: ${node.name} → ${next.name} (immediate=$immediate)',
          name: 'Node',
        );
        _failCounts.remove(next.id);
        state = state.copyWith(currentNode: next);
        return true;
      }

      // 所有节点都熔断了，重置全部
      _circuitOpenTimes.clear();
      _failCounts.clear();
    }
    return false;
  }

  /// 报告请求成功，重置失败计数
  void reportSuccess() {
    final node = state.currentNode;
    if (node == null) return;
    if (_failCounts.containsKey(node.id)) {
      _failCounts.remove(node.id);
    }
  }

  ServiceNode? _getNextAvailableNode() {
    final currentId = state.currentNode?.id;
    final available = state.nodes.where((n) {
      if (n.id == currentId) return false;
      return !_isCircuitOpen(n.id);
    }).toList();

    if (available.isEmpty) return null;

    // 优先选延迟最低的
    available.sort((a, b) {
      final la = state.latencyMap[a.id];
      final lb = state.latencyMap[b.id];
      if (la == null && lb == null) return 0;
      if (la == null) return 1;
      if (lb == null) return -1;
      return la.compareTo(lb);
    });

    return available.first;
  }

  bool _isCircuitOpen(int nodeId) {
    final openTime = _circuitOpenTimes[nodeId];
    if (openTime == null) return false;
    if (DateTime.now().difference(openTime).inSeconds >
        AppConfig.circuitBreakDurationSec) {
      // 半开：超过冷却期，允许重试
      _circuitOpenTimes.remove(nodeId);
      _failCounts.remove(nodeId);
      return false;
    }
    return true;
  }
}

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

final nodeProvider = NotifierProvider<NodeNotifier, NodeState>(
  NodeNotifier.new,
);
