import 'dart:async';

import 'package:flutter/foundation.dart';

import '../models/ble_models.dart';
import '../services/ble_service.dart';
import 'debug_provider.dart';

class BleProvider extends ChangeNotifier {
  BleProvider({BleService? bleService, required DebugProvider debugProvider})
      : _bleService = bleService ?? BleService(),
        _debugProvider = debugProvider;

  final BleService _bleService;
  final DebugProvider _debugProvider;

  StreamSubscription<List<BleDevice>>? _scanSubscription;
  StreamSubscription<BleStatus>? _statusSubscription;

  List<BleDevice> _devices = const <BleDevice>[];
  BleDevice? _selectedDevice;
  BleStatus? _latestStatus;
  ProvisioningState _provisioningState = ProvisioningState.scanning;
  String? _errorMessage;
  bool _isScanning = false;
  bool _isConnecting = false;
  bool _isProvisioning = false;
  bool _isSendingCommand = false;
  bool _isConnected = false;
  bool _isDisposed = false;

  List<BleDevice> get devices => _devices;
  BleDevice? get selectedDevice => _selectedDevice;
  BleStatus? get latestStatus => _latestStatus;
  ProvisioningState get provisioningState => _provisioningState;
  String? get errorMessage => _errorMessage;
  bool get isScanning => _isScanning;
  bool get isConnecting => _isConnecting;
  bool get isProvisioning => _isProvisioning;
  bool get isSendingCommand => _isSendingCommand;
  bool get isConnected => _isConnected;

  Future<void> startScan() async {
    _debugProvider.addBleLog('Start BLE scan');
    _errorMessage = null;
    _selectedDevice = null;
    _isConnected = false;
    _provisioningState = ProvisioningState.scanning;
    _isScanning = true;
    _notifySafely();

    await _scanSubscription?.cancel();
    _scanSubscription = _bleService
        .scanForShowenDevices()
        .listen((List<BleDevice> scannedDevices) {
      _devices = scannedDevices;
      _debugProvider.addBleLog(
        'BLE scan update (${scannedDevices.length} devices)',
      );
      _notifySafely();
    }, onError: (Object error, StackTrace stackTrace) {
      _errorMessage = error.toString();
      _isScanning = false;
      _provisioningState = ProvisioningState.failed;
      _debugProvider.addBleLog('BLE scan failed', details: error);
      _notifySafely();
    });

    Future<void>.delayed(const Duration(seconds: 6), () {
      if (_isScanning) {
        _isScanning = false;
        _debugProvider.addBleLog('BLE scan completed');
        _notifySafely();
      }
    });
  }

  Future<void> connectToDevice(BleDevice device) async {
    _debugProvider.addBleLog(
      'Connect BLE device ${device.name.isNotEmpty ? device.name : device.id}',
    );
    _selectedDevice = device;
    _errorMessage = null;
    _isConnecting = true;
    _isScanning = false;
    _provisioningState = ProvisioningState.connecting;
    _notifySafely();

    try {
      await _bleService.connectToDevice(device);
      await _subscribeToStatus();
      _isConnected = true;
      _debugProvider.addBleLog('BLE device connected');
    } catch (error) {
      _isConnected = false;
      _errorMessage = error.toString();
      _provisioningState = ProvisioningState.failed;
      _debugProvider.addBleLog('BLE connect failed', details: error);
      rethrow;
    } finally {
      _isConnecting = false;
      _notifySafely();
    }
  }

  Future<void> provisionWifi(String ssid, String password) async {
    _debugProvider.addBleLog(
      'Provision WiFi over BLE',
      details: <String, Object>{'ssid': ssid},
    );
    _errorMessage = null;
    _latestStatus = null;
    _isProvisioning = true;
    _provisioningState = ProvisioningState.writingCredentials;
    _notifySafely();

    try {
      final Future<BleStatus> operation = _bleService.provisionWifi(
        ssid,
        password,
        timeout: const Duration(seconds: 30),
      );

      Future<void>.delayed(const Duration(milliseconds: 400), () {
        if (_isProvisioning &&
            _provisioningState == ProvisioningState.writingCredentials) {
          _provisioningState = ProvisioningState.connectingWifi;
          _notifySafely();
        }
      });

      final BleStatus result = await operation;
      _latestStatus = result;
      _provisioningState = result.ok
          ? ProvisioningState.success
          : ProvisioningState.failed;
      if (!result.ok) {
        _errorMessage = result.error ?? 'WiFi provisioning failed';
        _debugProvider.addBleLog('BLE provisioning returned failure', details: result.error);
      } else {
        _debugProvider.addBleLog('BLE provisioning succeeded');
      }
    } on TimeoutException {
      _errorMessage = 'BLE 配网超时（30 秒）';
      _provisioningState = ProvisioningState.failed;
      _debugProvider.addBleLog('BLE provisioning timed out');
      rethrow;
    } catch (error) {
      _errorMessage = error.toString();
      _provisioningState = ProvisioningState.failed;
      _debugProvider.addBleLog('BLE provisioning failed', details: error);
      rethrow;
    } finally {
      _isProvisioning = false;
      _notifySafely();
    }
  }

  Future<void> disconnect() async {
    await _scanSubscription?.cancel();
    await _statusSubscription?.cancel();
    _scanSubscription = null;
    _statusSubscription = null;
    await _bleService.disconnect();
    _isConnected = false;
    _isConnecting = false;
    _isProvisioning = false;
    _selectedDevice = null;
    _debugProvider.addBleLog('BLE disconnected');
    _notifySafely();
  }

  Future<void> sendCommand(String command) async {
    _debugProvider.addBleLog('Send BLE command', details: command);
    _errorMessage = null;
    _isSendingCommand = true;
    _notifySafely();

    try {
      await _bleService.sendCommand(command);
      _debugProvider.addBleLog('BLE command sent', details: command);
    } catch (error) {
      _errorMessage = error.toString();
      _debugProvider.addBleLog('BLE command failed', details: error);
      rethrow;
    } finally {
      _isSendingCommand = false;
      _notifySafely();
    }
  }

  Future<void> retryScan() async {
    _debugProvider.addBleLog('Retry BLE scan');
    await disconnect();
    _devices = const <BleDevice>[];
    _latestStatus = null;
    _errorMessage = null;
    _provisioningState = ProvisioningState.scanning;
    _notifySafely();
    await startScan();
  }

  Future<void> _subscribeToStatus() async {
    await _statusSubscription?.cancel();
    final Stream<BleStatus> stream = await _bleService.subscribeToStatus();
    _statusSubscription = stream.listen((BleStatus status) {
      _latestStatus = status;
      _debugProvider.addBleLog(
        'BLE status update',
        details: <String, Object?>{
          'ok': status.ok,
          'action': status.action,
          'state': status.state,
          'error': status.error,
        },
      );
      if (!status.ok) {
        _errorMessage = status.error ?? 'BLE status returned an error';
      }
      if (status.action == 'connect') {
        if (!status.ok) {
          _provisioningState = ProvisioningState.failed;
        } else if (!status.isQueued) {
          _provisioningState = ProvisioningState.success;
        } else if (_isProvisioning) {
          _provisioningState = ProvisioningState.connectingWifi;
        }
      }
      _notifySafely();
    }, onError: (Object error, StackTrace stackTrace) {
      _errorMessage = error.toString();
      _provisioningState = ProvisioningState.failed;
      _debugProvider.addBleLog('BLE status stream failed', details: error);
      _notifySafely();
    });
  }

  @override
  void dispose() {
    _isDisposed = true;
    unawaited(_scanSubscription?.cancel());
    unawaited(_statusSubscription?.cancel());
    unawaited(_bleService.dispose());
    super.dispose();
  }

  void _notifySafely() {
    if (_isDisposed) {
      return;
    }
    notifyListeners();
  }
}
