import 'dart:async';

import 'package:flutter/foundation.dart';

import '../models/ble_status.dart';
import '../models/device_status.dart';
import '../models/player_status.dart';
import '../models/wifi_status.dart';
import '../services/device_storage_service.dart';
import '../services/http_api_service.dart';
import '../services/web_socket_service.dart';
import 'debug_provider.dart';

class DeviceProvider extends ChangeNotifier {
  DeviceProvider({
    required HttpApiService httpApiService,
    required WebSocketService webSocketService,
    required DeviceStorageService deviceStorageService,
    required DebugProvider debugProvider,
    String initialDeviceIp = '127.0.0.1',
    int initialDevicePort = 5000,
    String? initialDeviceName,
  })  : _httpApiService = httpApiService,
        _webSocketService = webSocketService,
        _debugProvider = debugProvider,
        _deviceStorageService = deviceStorageService,
        _deviceIp = _normalizeDeviceIp(initialDeviceIp),
        _devicePort = _normalizePort(initialDevicePort),
        _deviceName = _normalizeDeviceName(initialDeviceName) {
    _httpApiService.baseUrl = _buildBaseUrl(_deviceIp, _devicePort);
    _connectionSubscription = _webSocketService.onConnectionChanged.listen(
      _handleConnectionChanged,
    );
    _statusSubscription = _webSocketService.onStatusUpdate.listen(
      _handleStatusUpdate,
    );
    _wifiSubscription =
        _webSocketService.onWifiUpdate.listen(_handleWifiUpdate);
    _bleSubscription = _webSocketService.onBleUpdate.listen(_handleBleUpdate);
  }

  final HttpApiService _httpApiService;
  final WebSocketService _webSocketService;
  final DebugProvider _debugProvider;
  final DeviceStorageService _deviceStorageService;

  late final StreamSubscription<SocketConnectionStatus> _connectionSubscription;
  late final StreamSubscription<Map<String, dynamic>> _statusSubscription;
  late final StreamSubscription<Map<String, dynamic>> _wifiSubscription;
  late final StreamSubscription<Map<String, dynamic>> _bleSubscription;

  DeviceStatus _status = DeviceStatus.initial();
  bool _isLoading = false;
  String? _errorMessage;
  bool _webSocketConnected = false;
  String _deviceIp;
  int _devicePort;
  String _deviceName;
  List<SavedDevice> _deviceList = const <SavedDevice>[];

  DeviceStatus get status => _status;
  bool get isLoading => _isLoading;
  String? get errorMessage => _errorMessage;
  bool get webSocketConnected => _webSocketConnected;
  String get deviceIp => _deviceIp;
  int get devicePort => _devicePort;
  List<SavedDevice> get deviceList =>
      List<SavedDevice>.unmodifiable(_deviceList);
  HttpApiService get httpApiService => _httpApiService;

  Future<void> initialize() async {
    _debugProvider.addHttpLog(
      'Initialize device provider',
      details: <String, Object>{'device': '$_deviceIp:$_devicePort'},
    );
    final lastDevice = await _deviceStorageService.getLastDevice();
    if (lastDevice != null) {
      _applyDevice(
          ip: lastDevice.ip, port: lastDevice.port, name: lastDevice.name);
    }
    await _refreshDeviceList();
    await refresh();
    await connect();
  }

  Future<void> refresh() async {
    _setLoading(true);
    _debugProvider.addHttpLog(
      'Refresh device overview',
      details: <String, Object>{'device': '$_deviceIp:$_devicePort'},
    );
    try {
      final results = await Future.wait<dynamic>([
        _httpApiService.getPlaybackStatus(),
        _httpApiService.getWifiStatus(),
        _httpApiService.getBleStatus(),
      ]);

      _status = _buildStatus(
        playerStatus: results[0] as PlayerStatus,
        wifiStatus: results[1] as WifiStatus,
        bleStatus: results[2] as BleServiceStatus,
      );
      _errorMessage = null;
      _debugProvider.addHttpLog('Device overview refreshed');
    } catch (error) {
      _errorMessage = error.toString();
      _debugProvider.addHttpLog(
        'Refresh device overview failed',
        details: error,
      );
      _status = _status.copyWith(
        connected: false,
        connectionType: 'offline',
        deviceName: _deviceName,
        ipAddress: '$_deviceIp:$_devicePort',
      );
    } finally {
      _setLoading(false);
    }
  }

  Future<void> loadDeviceOverview() => refresh();

  Future<void> connect() async {
    _debugProvider.addWsLog(
      'Connect WebSocket',
      details: <String, Object>{'device': '$_deviceIp:$_devicePort'},
    );
    try {
      await _webSocketService.connect(_deviceIp, port: _devicePort);
      _webSocketConnected = _webSocketService.isConnected;
      _errorMessage = null;
      _debugProvider.addWsLog('WebSocket connect request completed');
      notifyListeners();
    } catch (error) {
      _webSocketConnected = false;
      _errorMessage = error.toString();
      _debugProvider.addWsLog('WebSocket connect failed', details: error);
      notifyListeners();
    }
  }

  Future<void> switchDevice(String input, {String? name}) async {
    final nextDevice = _parseDeviceInput(input, fallbackPort: _devicePort);
    final nextName =
        _normalizeDeviceName(name ?? _status.deviceName ?? _deviceName);
    _setLoading(true);
    _debugProvider.addHttpLog(
      'Switch device to ${nextDevice.ip}:${nextDevice.port}',
      details: <String, Object>{'name': nextName},
    );
    try {
      await _validateDeviceReachable(nextDevice.ip, nextDevice.port);

      await _webSocketService.disconnect();
      _applyDevice(ip: nextDevice.ip, port: nextDevice.port, name: nextName);
      notifyListeners();

      await _deviceStorageService.saveDevice(
          nextDevice.ip, nextDevice.port, nextName);
      await _refreshDeviceList();
      await refresh();
      await connect();

      final resolvedName = _normalizeDeviceName(_status.deviceName ?? nextName);
      if (resolvedName != nextName) {
        _deviceName = resolvedName;
        await _deviceStorageService.saveDevice(
          nextDevice.ip,
          nextDevice.port,
          resolvedName,
        );
        await _refreshDeviceList();
        notifyListeners();
      }
      _debugProvider.addHttpLog(
        'Device switched successfully',
        details: <String, Object>{'device': '${nextDevice.ip}:${nextDevice.port}'},
      );
    } catch (error) {
      _errorMessage = error.toString();
      _debugProvider.addHttpLog('Switch device failed', details: error);
      notifyListeners();
      rethrow;
    } finally {
      _setLoading(false);
    }
  }

  Future<void> updateDeviceIp(String ip) async {
    await switchDevice(ip);
  }

  Future<void> removeStoredDevice(SavedDevice device) async {
    await _deviceStorageService.removeDevice(device.ip, device.port);
    await _refreshDeviceList();
    _debugProvider.addHttpLog(
      'Removed saved device ${device.address}',
      details: <String, Object>{'name': device.name},
    );
    notifyListeners();
  }

  Future<void> startBle({String? deviceName}) async {
    _setLoading(true);
    _debugProvider.addHttpLog(
      'Start BLE service',
      details: <String, Object?>{'deviceName': deviceName},
    );
    try {
      await _httpApiService.startBle(deviceName);
      await refresh();
      _debugProvider.addHttpLog('BLE service started');
    } catch (error) {
      _errorMessage = error.toString();
      _debugProvider.addHttpLog('Start BLE service failed', details: error);
      notifyListeners();
    } finally {
      _setLoading(false);
    }
  }

  Future<void> stopBle() async {
    _setLoading(true);
    _debugProvider.addHttpLog('Stop BLE service');
    try {
      await _httpApiService.stopBle();
      await refresh();
      _debugProvider.addHttpLog('BLE service stopped');
    } catch (error) {
      _errorMessage = error.toString();
      _debugProvider.addHttpLog('Stop BLE service failed', details: error);
      notifyListeners();
    } finally {
      _setLoading(false);
    }
  }

  void _handleConnectionChanged(SocketConnectionStatus connectionStatus) {
    _webSocketConnected = connectionStatus == SocketConnectionStatus.connected;
    _debugProvider.addWsLog(
      'Device provider connection state: ${connectionStatus.name}',
    );
    if (!_webSocketConnected) {
      _status = _status.copyWith(connectionType: _status.connectionType);
    }
    notifyListeners();
  }

  void _handleStatusUpdate(Map<String, dynamic> payload) {
    _debugProvider.addWsLog('Received status update', details: payload);
    final playerStatus = PlayerStatus.fromJson(payload);
    _status = _buildStatus(
      playerStatus: playerStatus,
      wifiStatus: _status.wifiStatus ?? WifiStatus.disconnected(),
      bleStatus: _status.bleStatus ?? BleServiceStatus.initial(),
    );
    notifyListeners();
  }

  void _handleWifiUpdate(Map<String, dynamic> payload) {
    _debugProvider.addWsLog('Received wifi update', details: payload);
    final wifiStatus = WifiStatus.fromJson(payload);
    _status = _buildStatus(
      playerStatus: _status.playerStatus ?? PlayerStatus.initial(),
      wifiStatus: wifiStatus,
      bleStatus: _status.bleStatus ?? BleServiceStatus.initial(),
    );
    notifyListeners();
  }

  void _handleBleUpdate(Map<String, dynamic> payload) {
    _debugProvider.addBleLog('Received BLE update', details: payload);
    final normalized = <String, dynamic>{
      'running': payload['running'] ?? payload['ready'] ?? false,
      'embedded': payload['embedded'] ?? false,
      'device_name': payload['device_name'] ?? payload['name'],
    };
    final bleStatus = BleServiceStatus.fromJson(normalized);
    _status = _buildStatus(
      playerStatus: _status.playerStatus ?? PlayerStatus.initial(),
      wifiStatus: _status.wifiStatus ?? WifiStatus.disconnected(),
      bleStatus: bleStatus,
    );
    notifyListeners();
  }

  DeviceStatus _buildStatus({
    required PlayerStatus playerStatus,
    required WifiStatus wifiStatus,
    required BleServiceStatus bleStatus,
  }) {
    final connectionType = wifiStatus.connected
        ? 'wifi'
        : bleStatus.running
            ? 'ble'
            : 'offline';

    return DeviceStatus(
      connected:
          wifiStatus.connected || bleStatus.running || _webSocketConnected,
      connectionType: connectionType,
      deviceName: bleStatus.deviceName ?? _deviceName,
      ipAddress: wifiStatus.ip ?? '$_deviceIp:$_devicePort',
      playerStatus: playerStatus,
      wifiStatus: wifiStatus,
      bleStatus: bleStatus,
      updatedAt: DateTime.now(),
    );
  }

  Future<void> _refreshDeviceList() async {
    _deviceList = await _deviceStorageService.getDevices();
  }

  Future<void> _validateDeviceReachable(String ip, int port) async {
    final probeService = HttpApiService(baseUrl: _buildBaseUrl(ip, port));
    try {
      await probeService.getStatus().timeout(const Duration(seconds: 3));
    } on TimeoutException {
      throw Exception('设备不可达：连接超时（3 秒）');
    } on Exception catch (error) {
      throw Exception('设备不可达：$error');
    } finally {
      probeService.dispose();
    }
  }

  void _applyDevice({
    required String ip,
    required int port,
    String? name,
  }) {
    _deviceIp = _normalizeDeviceIp(ip);
    _devicePort = _normalizePort(port);
    _deviceName = _normalizeDeviceName(name);
    _httpApiService.baseUrl = _buildBaseUrl(_deviceIp, _devicePort);
    _status = _status.copyWith(
      deviceName: _deviceName,
      ipAddress: '$_deviceIp:$_devicePort',
      updatedAt: DateTime.now(),
    );
  }

  void _setLoading(bool value) {
    _isLoading = value;
    notifyListeners();
  }

  static String _buildBaseUrl(String ip, int port) => 'http://$ip:$port';

  static String _normalizeDeviceIp(String input) {
    var value = input.trim();
    if (value.startsWith('http://')) {
      value = value.substring(7);
    } else if (value.startsWith('https://')) {
      value = value.substring(8);
    } else if (value.startsWith('ws://')) {
      value = value.substring(5);
    } else if (value.startsWith('wss://')) {
      value = value.substring(6);
    }
    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.isEmpty ? '127.0.0.1' : value;
  }

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

  static String _normalizeDeviceName(String? input) {
    final value = input?.trim() ?? '';
    return value.isEmpty ? 'Showen' : value;
  }

  static SavedDevice _parseDeviceInput(String input,
      {int fallbackPort = 5000}) {
    var value = input.trim();
    if (value.startsWith('http://')) {
      value = value.substring(7);
    } else if (value.startsWith('https://')) {
      value = value.substring(8);
    } else if (value.startsWith('ws://')) {
      value = value.substring(5);
    } else if (value.startsWith('wss://')) {
      value = value.substring(6);
    }

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

    final colonIndex = value.lastIndexOf(':');
    final hasPort = colonIndex > 0 && colonIndex < value.length - 1;
    final ip =
        _normalizeDeviceIp(hasPort ? value.substring(0, colonIndex) : value);
    final port = hasPort
        ? _normalizePort(
            int.tryParse(value.substring(colonIndex + 1)) ?? fallbackPort)
        : _normalizePort(fallbackPort);

    return SavedDevice(
      ip: ip,
      port: port,
      name: 'Showen',
      lastUsedAt: DateTime.now(),
    );
  }

  @override
  void dispose() {
    unawaited(_connectionSubscription.cancel());
    unawaited(_statusSubscription.cancel());
    unawaited(_wifiSubscription.cancel());
    unawaited(_bleSubscription.cancel());
    super.dispose();
  }
}
