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

import 'package:web_socket_channel/web_socket_channel.dart';

import '../models/app_event.dart';

enum WsConnectionState { connected, connecting, disconnected }

@Deprecated('Use WsConnectionState instead')
enum SocketConnectionStatus { disconnected, connecting, connected }

class WebSocketService {
  static const Duration _initialReconnectDelay = Duration(seconds: 2);
  static const Duration _maxReconnectDelay = Duration(seconds: 60);

  WebSocketChannel? _channel;
  StreamSubscription<dynamic>? _subscription;
  Timer? _reconnectTimer;
  String? _deviceIp;
  int _devicePort = 5000;
  bool _manualDisconnect = false;
  WsConnectionState _connectionState = WsConnectionState.disconnected;
  int _retryCount = 0;
  Duration _nextReconnectDelay = _initialReconnectDelay;

  final StreamController<AppEvent> _eventController =
      StreamController<AppEvent>.broadcast();
  final StreamController<Map<String, dynamic>> _statusController =
      StreamController<Map<String, dynamic>>.broadcast();
  final StreamController<Map<String, dynamic>> _stateController =
      StreamController<Map<String, dynamic>>.broadcast();
  final StreamController<Map<String, dynamic>> _configController =
      StreamController<Map<String, dynamic>>.broadcast();
  final StreamController<Map<String, dynamic>> _wifiController =
      StreamController<Map<String, dynamic>>.broadcast();
  final StreamController<Map<String, dynamic>> _bleController =
      StreamController<Map<String, dynamic>>.broadcast();
  final StreamController<WsConnectionState> _connectionStateController =
      StreamController<WsConnectionState>.broadcast();

  Stream<AppEvent> get events => _eventController.stream;
  Stream<Map<String, dynamic>> get onStatusUpdate => _statusController.stream;
  Stream<Map<String, dynamic>> get onStateUpdate => _stateController.stream;
  Stream<Map<String, dynamic>> get onConfigUpdate => _configController.stream;
  Stream<Map<String, dynamic>> get onWifiUpdate => _wifiController.stream;
  Stream<Map<String, dynamic>> get onBleUpdate => _bleController.stream;
  Stream<SocketConnectionStatus> get connectionState =>
      _connectionStateController.stream.map(_toLegacyConnectionStatus);
  Stream<WsConnectionState> get connectionStateStream =>
      _connectionStateController.stream;
  Stream<SocketConnectionStatus> get onConnectionChanged =>
      _connectionStateController.stream.map(_toLegacyConnectionStatus);

  WsConnectionState get wsConnectionState => _connectionState;
  SocketConnectionStatus get connectionStatus =>
      _toLegacyConnectionStatus(_connectionState);
  bool get isConnected => _connectionState == WsConnectionState.connected;
  int get retryCount => _retryCount;

  Future<void> connect(String deviceIp, {int port = 5000}) async {
    _manualDisconnect = false;
    _deviceIp = _normalizeDeviceIp(deviceIp);
    _devicePort = _normalizePort(port);
    _reconnectTimer?.cancel();

    await _establishConnection(resetBackoff: true);
  }

  Future<void> manualReconnect() async {
    final deviceIp = _deviceIp;
    if (deviceIp == null || deviceIp.isEmpty) {
      return;
    }

    _manualDisconnect = false;
    _reconnectTimer?.cancel();
    await _establishConnection(resetBackoff: true);
  }

  Future<void> _establishConnection({bool resetBackoff = false}) async {
    if (resetBackoff) {
      _retryCount = 0;
      _nextReconnectDelay = _initialReconnectDelay;
    }

    await _subscription?.cancel();
    _subscription = null;
    await _channel?.sink.close();
    _channel = null;

    _setConnectionState(WsConnectionState.connecting);

    try {
      final url = Uri.parse('ws://$_deviceIp:$_devicePort/ws');
      _channel = WebSocketChannel.connect(url);
      await _channel!.ready;
      _subscription = _channel!.stream.listen(
        _handleMessage,
        onDone: _handleSocketClosed,
        onError: (_) => _handleSocketClosed(),
        cancelOnError: true,
      );

      _retryCount = 0;
      _nextReconnectDelay = _initialReconnectDelay;
      _setConnectionState(WsConnectionState.connected);
    } catch (_) {
      _channel = null;
      _subscription = null;
      if (_manualDisconnect) {
        _setConnectionState(WsConnectionState.disconnected);
        return;
      }

      await reconnect();
    }
  }

  void sendCommand(Map<String, dynamic> command) {
    if (!isConnected || _channel == null) {
      throw StateError('WebSocket 未连接');
    }
    _channel!.sink.add(jsonEncode(command));
  }

  Future<void> reconnect() async {
    final deviceIp = _deviceIp;
    if (deviceIp == null || deviceIp.isEmpty || _manualDisconnect) {
      return;
    }

    _setConnectionState(WsConnectionState.connecting);
    _scheduleReconnect();
  }

  void _scheduleReconnect() {
    if (_manualDisconnect) {
      return;
    }

    _reconnectTimer?.cancel();
    final delay = _nextReconnectDelay;
    _retryCount += 1;
    _nextReconnectDelay = _nextReconnectDelay * 2;
    if (_nextReconnectDelay > _maxReconnectDelay) {
      _nextReconnectDelay = _maxReconnectDelay;
    }

    _reconnectTimer = Timer(delay, () {
      unawaited(_establishConnection());
    });
  }

  Future<void> disconnect() async {
    _manualDisconnect = true;
    _reconnectTimer?.cancel();
    _retryCount = 0;
    _nextReconnectDelay = _initialReconnectDelay;
    await _subscription?.cancel();
    _subscription = null;
    await _channel?.sink.close();
    _channel = null;
    _setConnectionState(WsConnectionState.disconnected);
  }

  Future<void> dispose() async {
    await disconnect();
    await _eventController.close();
    await _statusController.close();
    await _stateController.close();
    await _configController.close();
    await _wifiController.close();
    await _bleController.close();
    await _connectionStateController.close();
  }

  void _handleMessage(dynamic data) {
    final raw = data is String ? data : utf8.decode(data as List<int>);
    final decoded = jsonDecode(raw);
    if (decoded is! Map) {
      return;
    }

    final event = AppEvent.fromJson(Map<String, dynamic>.from(decoded));
    _eventController.add(event);

    switch (event.type) {
      case 'status_update':
        _statusController.add(event.payload);
        break;
      case 'state_update':
        _stateController.add(event.payload);
        break;
      case 'config_update':
        _configController.add(event.payload);
        break;
      case 'wifi_update':
        _wifiController.add(event.payload);
        break;
      case 'ble_update':
        _bleController.add(event.payload);
        break;
    }
  }

  void _handleSocketClosed() {
    _channel = null;
    _subscription = null;
    if (_manualDisconnect) {
      _retryCount = 0;
      _nextReconnectDelay = _initialReconnectDelay;
      _setConnectionState(WsConnectionState.disconnected);
      return;
    }

    unawaited(reconnect());
  }

  void _setConnectionState(WsConnectionState state) {
    _connectionState = state;
    _connectionStateController.add(state);
  }

  SocketConnectionStatus _toLegacyConnectionStatus(WsConnectionState state) {
    switch (state) {
      case WsConnectionState.connected:
        return SocketConnectionStatus.connected;
      case WsConnectionState.connecting:
        return SocketConnectionStatus.connecting;
      case WsConnectionState.disconnected:
        return SocketConnectionStatus.disconnected;
    }
  }

  String _normalizeDeviceIp(String raw) {
    var value = raw.trim();
    if (value.startsWith('ws://')) {
      value = value.substring(5);
    } else if (value.startsWith('wss://')) {
      value = value.substring(6);
    } else if (value.startsWith('http://')) {
      value = value.substring(7);
    } else if (value.startsWith('https://')) {
      value = value.substring(8);
    }

    final slashIndex = value.indexOf('/');
    if (slashIndex >= 0) {
      value = value.substring(0, slashIndex);
    }

    final colonIndex = value.indexOf(':');
    if (colonIndex >= 0) {
      value = value.substring(0, colonIndex);
    }

    return value;
  }

  int _normalizePort(int value) {
    if (value <= 0 || value > 65535) {
      return 5000;
    }
    return value;
  }
}
