import 'dart:async';
import 'dart:convert';
import 'package:web_socket_channel/io.dart';
import 'package:web_socket_channel/status.dart' as status;

import 'connection_logger.dart';
import 'connection_state.dart';
import 'retry_strategy.dart';
import 'health_monitor.dart';


class ConnectionManagerSimple {
  static final ConnectionManagerSimple _instance =
      ConnectionManagerSimple._internal();
  factory ConnectionManagerSimple() => _instance;
  ConnectionManagerSimple._internal();


  final ConnectionLogger _logger = ConnectionLogger();
  final ConnectionStateManager _stateManager = ConnectionStateManager();
  final RetryManager _retryManager = RetryManager();
  final HealthMonitor _healthMonitor = HealthMonitor();


  IOWebSocketChannel? _channel;
  StreamSubscription? _messageSubscription;


  final List<String> _serverUrls = [
    'wss://ws-api.oneme.ru:443/websocket',
    'wss://ws-api.oneme.ru/websocket',
    'wss://ws-api.oneme.ru:8443/websocket',
    'ws://ws-api.oneme.ru:80/websocket',
    'ws://ws-api.oneme.ru/websocket',
    'ws://ws-api.oneme.ru:8080/websocket',
  ];

  int _currentUrlIndex = 0;
  String? _currentServerUrl;


  bool _isConnecting = false;
  bool _isDisposed = false;
  int _sequenceNumber = 0;
  String? _authToken;


  final List<Map<String, dynamic>> _messageQueue = [];


  Timer? _pingTimer;
  Timer? _reconnectTimer;
  Timer? _responseTimeoutTimer;
  Timer? _connectionTimeoutTimer;


  final StreamController<Map<String, dynamic>> _messageController =
      StreamController<Map<String, dynamic>>.broadcast();
  final StreamController<String> _connectionStatusController =
      StreamController<String>.broadcast();


  Stream<Map<String, dynamic>> get messageStream => _messageController.stream;


  Stream<String> get connectionStatusStream =>
      _connectionStatusController.stream;


  Stream<ConnectionInfo> get stateStream => _stateManager.stateStream;


  Stream<LogEntry> get logStream => _logger.logStream;


  Stream<HealthMetrics> get healthMetricsStream => _healthMonitor.metricsStream;


  ConnectionInfo get currentState => _stateManager.currentInfo;


  bool get isConnected => currentState.isActive;


  bool get canSendMessages => currentState.canSendMessages;


  Future<void> initialize() async {
    if (_isDisposed) {
      _logger.logError('Попытка инициализации после dispose');
      return;
    }

    _logger.logConnection('Инициализация ConnectionManagerSimple');
    _stateManager.setState(
      ConnectionState.disconnected,
      message: 'Инициализация',
    );
  }


  Future<void> connect({String? authToken}) async {
    if (_isDisposed) {
      _logger.logError('Попытка подключения после dispose');
      return;
    }

    if (_isConnecting) {
      _logger.logConnection('Подключение уже в процессе');
      return;
    }

    _authToken = authToken;
    _isConnecting = true;

    _logger.logConnection(
      'Начало подключения',
      data: {
        'auth_token_present': authToken != null,
        'server_count': _serverUrls.length,
      },
    );

    _stateManager.setState(
      ConnectionState.connecting,
      message: 'Подключение к серверу',
      attemptNumber: 1,
    );

    try {
      await _connectWithFallback();
    } catch (e) {
      _logger.logError('Ошибка подключения', error: e);
      _stateManager.setState(
        ConnectionState.error,
        message: 'Ошибка подключения: ${e.toString()}',
      );
      rethrow;
    } finally {
      _isConnecting = false;
    }
  }


  Future<void> _connectWithFallback() async {
    final sessionId = 'connect_${DateTime.now().millisecondsSinceEpoch}';
    final session = _retryManager.startSession(sessionId, ErrorType.network);

    while (_currentUrlIndex < _serverUrls.length) {
      final url = _serverUrls[_currentUrlIndex];
      _currentServerUrl = url;

      _logger.logConnection(
        'Попытка подключения',
        data: {
          'url': url,
          'attempt': _currentUrlIndex + 1,
          'total_servers': _serverUrls.length,
        },
      );

      try {
        await _connectToUrl(url);

        _logger.logConnection(
          'Успешное подключение',
          data: {'url': url, 'server_index': _currentUrlIndex},
        );

        _stateManager.setState(
          ConnectionState.connected,
          message: 'Подключен к серверу',
          serverUrl: url,
        );

        _healthMonitor.startMonitoring(serverUrl: url);
        _retryManager.endSession(sessionId);
        return;
      } catch (e) {
        final errorInfo = ErrorInfo(
          type: _getErrorType(e),
          message: e.toString(),
          timestamp: DateTime.now(),
        );

        session.addAttempt(errorInfo.type, message: e.toString());

        _logger.logError(
          'Ошибка подключения к серверу',
          data: {
            'url': url,
            'error': e.toString(),
            'error_type': errorInfo.type.name,
          },
        );

        _currentUrlIndex++;

        if (_currentUrlIndex < _serverUrls.length) {
          final delay = Duration(milliseconds: 500);
          _logger.logConnection(
            'Переход к следующему серверу через ${delay.inMilliseconds}ms',
          );
          await Future.delayed(delay);
        }
      }
    }


    _logger.logError(
      'Все серверы недоступны',
      data: {'total_servers': _serverUrls.length},
    );

    _stateManager.setState(
      ConnectionState.error,
      message: 'Все серверы недоступны',
    );

    throw Exception('Не удалось подключиться ни к одному серверу');
  }


  Future<void> _connectToUrl(String url) async {
    final uri = Uri.parse(url);

    _logger.logConnection(
      'Подключение к URL',
      data: {'host': uri.host, 'port': uri.port, 'scheme': uri.scheme},
    );


    final headers = <String, String>{
      'Origin': 'https://web.max.ru',
      'User-Agent':
          'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Sec-WebSocket-Extensions': 'permessage-deflate',
    };

    _channel = IOWebSocketChannel.connect(uri, headers: headers);


    _connectionTimeoutTimer = Timer(const Duration(seconds: 10), () {
      _logger.logError('Таймаут подключения к серверу');
      _handleConnectionTimeout();
    });

    await _channel!.ready;
    _connectionTimeoutTimer?.cancel();

    _logger.logConnection('WebSocket канал готов');

    _setupMessageListener();
    await _sendHandshake();
    _startPingTimer();
  }


  void _setupMessageListener() {
    _messageSubscription?.cancel();

    _messageSubscription = _channel!.stream.listen(
      _handleMessage,
      onError: _handleError,
      onDone: _handleDisconnection,
      cancelOnError: true,
    );

    _logger.logConnection('Слушатель сообщений настроен');
  }


  void _handleMessage(dynamic message) {
    if (message == null || (message is String && message.trim().isEmpty)) {
      return;
    }

    try {
      _logger.logMessage('IN', message);

      final decodedMessage = message is String ? jsonDecode(message) : message;


      if (decodedMessage is Map && decodedMessage['opcode'] == 1) {
        _healthMonitor.onPongReceived();

        _responseTimeoutTimer?.cancel();
      }


      if (decodedMessage is Map &&
          decodedMessage['opcode'] == 6 &&
          decodedMessage['cmd'] == 1) {
        _handleHandshakeSuccess(Map<String, dynamic>.from(decodedMessage));
        return;
      }


      if (decodedMessage is Map && decodedMessage['cmd'] == 3) {
        _handleServerError(Map<String, dynamic>.from(decodedMessage));
        return;
      }


      if (decodedMessage is Map &&
          decodedMessage['opcode'] == 97 &&
          decodedMessage['cmd'] == 1) {
        _handleSessionTermination();
        return;
      }

      _messageController.add(decodedMessage);
    } catch (e) {
      _logger.logError(
        'Ошибка обработки сообщения',
        data: {'message': message.toString(), 'error': e.toString()},
      );
    }
  }


  void _handleHandshakeSuccess(Map<String, dynamic> message) {
    _logger.logConnection(
      'Handshake успешен',
      data: {'payload': message['payload']},
    );


    _responseTimeoutTimer?.cancel();

    _stateManager.setState(
      ConnectionState.ready,
      message: 'Сессия готова к работе',
    );

    _processMessageQueue();
  }


  void _handleServerError(Map<String, dynamic> message) {
    final error = message['payload'];
    _logger.logError('Ошибка сервера', data: {'error': error});

    if (error != null) {
      if (error['error'] == 'proto.state') {
        _logger.logConnection('Ошибка состояния сессии, переподключаемся');
        _scheduleReconnect('Ошибка состояния сессии');
      } else if (error['error'] == 'login.token') {
        _logger.logConnection('Недействительный токен');
        _handleInvalidToken();
      }
    }
  }


  void _handleSessionTermination() {
    _logger.logConnection('Сессия завершена сервером');
    _stateManager.setState(
      ConnectionState.disconnected,
      message: 'Сессия завершена сервером',
    );
    _clearAuthData();
  }


  void _handleInvalidToken() {
    _logger.logConnection('Обработка недействительного токена');
    _clearAuthData();
    _stateManager.setState(
      ConnectionState.disconnected,
      message: 'Требуется повторная авторизация',
    );
  }


  void _handleConnectionTimeout() {
    _logger.logError('Таймаут подключения к серверу');
    _stateManager.setState(
      ConnectionState.error,
      message: 'Таймаут подключения',
    );
    _scheduleReconnect('Таймаут подключения');
  }


  void _handleResponseTimeout() {
    _logger.logError('Таймаут ответа от сервера');
    _stateManager.setState(
      ConnectionState.error,
      message: 'Сервер не отвечает',
    );
    _scheduleReconnect('Сервер не отвечает');
  }


  void _clearAuthData() {
    _authToken = null;
    _logger.logConnection('Данные аутентификации очищены');
  }


  void _handleError(dynamic error) {
    _logger.logError('Ошибка WebSocket', error: error);
    _healthMonitor.onError(error.toString());
    _scheduleReconnect('Ошибка WebSocket: $error');
  }


  void _handleDisconnection() {
    _logger.logConnection('WebSocket соединение закрыто');
    _healthMonitor.onReconnect();
    _scheduleReconnect('Соединение закрыто');
  }


  void _scheduleReconnect(String reason) {
    if (_isDisposed) return;

    _reconnectTimer?.cancel();

    final sessionId = 'reconnect_${DateTime.now().millisecondsSinceEpoch}';
    final session = _retryManager.startSession(sessionId, ErrorType.network);

    if (!session.canRetry()) {
      _logger.logError(
        'Превышено максимальное количество попыток переподключения',
      );
      _stateManager.setState(
        ConnectionState.error,
        message: 'Не удалось переподключиться',
      );
      return;
    }

    final delay = session.getNextDelay();

    _logger.logReconnect(session.attemptCount + 1, reason, delay: delay);

    _stateManager.setState(
      ConnectionState.reconnecting,
      message: 'Переподключение через ${delay.inSeconds}с',
      reconnectDelay: delay,
    );

    _reconnectTimer = Timer(delay, () async {
      try {

        await _fullReconnect();
      } catch (e) {
        _logger.logError('Ошибка переподключения', error: e);
      }
    });
  }


  Future<void> _fullReconnect() async {
    _logger.logConnection('Начинаем полное переподключение');


    _cleanup();
    _currentUrlIndex = 0;
    _sequenceNumber = 0;
    _messageQueue.clear();


    _stateManager.setState(
      ConnectionState.disconnected,
      message: 'Подготовка к переподключению',
    );


    await Future.delayed(const Duration(milliseconds: 500));


    await connect(authToken: _authToken);
  }


  Future<void> _sendHandshake() async {
    _logger.logConnection('Отправка handshake');

    final payload = {
      "userAgent": {
        "deviceType": "WEB",
        "locale": "ru",
        "deviceLocale": "ru",
        "osVersion": "Windows",
        "deviceName": "Chrome",
        "headerUserAgent":
            "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
        "appVersion": "25.9.15",
        "screen": "1920x1080 1.0x",
        "timezone": "Europe/Moscow",
      },
      "deviceId": _generateDeviceId(),
    };

    _sendMessage(6, payload);


    _responseTimeoutTimer = Timer(const Duration(seconds: 15), () {
      _logger.logError('Таймаут ответа от сервера на handshake');
      _handleResponseTimeout();
    });
  }


  int _sendMessage(int opcode, Map<String, dynamic> payload) {
    if (_channel == null) {
      _logger.logError('WebSocket не подключен');
      return -1;
    }

    final message = {
      "ver": 11,
      "cmd": 0,
      "seq": _sequenceNumber,
      "opcode": opcode,
      "payload": payload,
    };

    final encodedMessage = jsonEncode(message);
    _logger.logMessage('OUT', encodedMessage);

    _channel!.sink.add(encodedMessage);
    return _sequenceNumber++;
  }


  int sendMessage(int opcode, Map<String, dynamic> payload) {
    if (!canSendMessages) {
      _logger.logConnection(
        'Сообщение добавлено в очередь',
        data: {'opcode': opcode, 'reason': 'Соединение не готово'},
      );
      _messageQueue.add({'opcode': opcode, 'payload': payload});
      return -1;
    }

    return _sendMessage(opcode, payload);
  }


  void _processMessageQueue() {
    if (_messageQueue.isEmpty) return;

    _logger.logConnection(
      'Обработка очереди сообщений',
      data: {'count': _messageQueue.length},
    );

    for (final message in _messageQueue) {
      _sendMessage(message['opcode'], message['payload']);
    }

    _messageQueue.clear();
  }


  void _startPingTimer() {
    _pingTimer?.cancel();
    _pingTimer = Timer.periodic(const Duration(seconds: 25), (_) {
      if (canSendMessages) {
        _logger.logConnection('Отправка ping');
        _sendMessage(1, {"interactive": true});


        _responseTimeoutTimer?.cancel();
        _responseTimeoutTimer = Timer(const Duration(seconds: 10), () {
          _logger.logError('Таймаут pong ответа от сервера');
          _handleResponseTimeout();
        });
      }
    });
  }


  String _generateDeviceId() {
    final timestamp = DateTime.now().millisecondsSinceEpoch;
    final random = (timestamp % 1000000).toString().padLeft(6, '0');
    return "$timestamp$random";
  }


  ErrorType _getErrorType(dynamic error) {
    final errorString = error.toString().toLowerCase();

    if (errorString.contains('timeout') ||
        errorString.contains('connection') ||
        errorString.contains('network')) {
      return ErrorType.network;
    }

    if (errorString.contains('unauthorized') ||
        errorString.contains('forbidden')) {
      return ErrorType.authentication;
    }

    if (errorString.contains('server') || errorString.contains('internal')) {
      return ErrorType.server;
    }

    return ErrorType.unknown;
  }


  Future<void> disconnect() async {
    _logger.logConnection('Отключение');

    _stateManager.setState(
      ConnectionState.disconnected,
      message: 'Отключение по запросу',
    );

    _stopMonitoring();
    _cleanup();
  }


  Future<void> forceReconnect() async {
    if (_isDisposed) {
      _logger.logError('Попытка переподключения после dispose');
      return;
    }

    _logger.logConnection('Принудительное переподключение');


    _reconnectTimer?.cancel();
    _pingTimer?.cancel();


    await _fullReconnect();
  }


  void _stopMonitoring() {
    _pingTimer?.cancel();
    _reconnectTimer?.cancel();
    _responseTimeoutTimer?.cancel();
    _connectionTimeoutTimer?.cancel();
    _messageSubscription?.cancel();
    _healthMonitor.stopMonitoring();
  }


  void _cleanup() {
    _channel?.sink.close(status.goingAway);
    _channel = null;
    _messageQueue.clear();
    _currentUrlIndex = 0;
    _sequenceNumber = 0;
  }


  Map<String, dynamic> getStatistics() {
    return {
      'connection_state': currentState.state.name,
      'health_metrics': _healthMonitor.getStatistics(),
      'retry_statistics': _retryManager.getStatistics(),
      'log_statistics': _logger.getLogStats(),
      'message_queue_size': _messageQueue.length,
      'current_server': _currentServerUrl,
      'server_index': _currentUrlIndex,
    };
  }


  void dispose() {
    if (_isDisposed) return;

    _logger.logConnection('Освобождение ресурсов ConnectionManagerSimple');

    _isDisposed = true;
    _stopMonitoring();
    _cleanup();

    _messageController.close();
    _connectionStatusController.close();
    _stateManager.dispose();
    _logger.dispose();
    _healthMonitor.dispose();
  }
}
