import 'dart:async';
import 'dart:math';

import 'package:flutter/foundation.dart' show VoidCallback;
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

import '../models/ble_device.dart';
import '../models/bp_reading.dart';

class UnsupportedBleDeviceException implements Exception {
  final String message;

  const UnsupportedBleDeviceException(this.message);

  @override
  String toString() => message;
}

class HealthBleService {
  static const bpMeasurementUuid = '00002A35-0000-1000-8000-00805F9B34FB';
  static const racpUuid = '00002A52-0000-1000-8000-00805F9B34FB';

  BluetoothDevice? _device;
  StreamSubscription<BluetoothConnectionState>? _connStateSub;
  StreamSubscription<List<int>>? _measurementSub;
  final _connStateController = StreamController<bool>.broadcast();

  Stream<bool> get connectionState => _connStateController.stream;

  static bool isSupportedHealthDevice(ScanResult result) {
    return BleDeviceIdentifier.isSupportedScanResult(result);
  }

  static BleDeviceType? deviceTypeFromScan(ScanResult result) {
    return BleDeviceIdentifier.typeFromScan(result);
  }

  static bool isSyncImplemented(BleDeviceType type) {
    return type == BleDeviceType.bloodPressure;
  }

  Future<BoundBleDevice> connectAndIdentify({
    required BluetoothDevice device,
    required String name,
  }) async {
    final services = await _connectAndDiscover(device);
    final type = BleDeviceIdentifier.typeFromServices(services);
    if (type == null) {
      throw const UnsupportedBleDeviceException('暂不支持该设备');
    }
    return BoundBleDevice(
      id: device.remoteId.toString(),
      name: name,
      type: type,
      serviceUuid: type.serviceUuid,
    );
  }

  Future<HealthBleSyncResult> syncBoundDevice(
    BluetoothDevice device,
    BoundBleDevice bound, {
    Duration timeout = const Duration(seconds: 45),
    VoidCallback? onConnected,
    VoidCallback? onMeasurementReceived,
  }) async {
    final services = await _connectAndDiscover(device);
    onConnected?.call();
    final connectedType = BleDeviceIdentifier.typeFromServices(services);
    if (connectedType != bound.type) {
      throw const UnsupportedBleDeviceException('设备类型和绑定信息不一致');
    }
    if (!isSyncImplemented(bound.type)) {
      throw UnsupportedBleDeviceException('${bound.type.label}数据同步暂未开通');
    }

    return switch (bound.type) {
      BleDeviceType.bloodPressure => BloodPressureSyncResult(
        device: bound,
        reading: await _syncBloodPressure(
          services,
          onMeasurementReceived: onMeasurementReceived,
        ).timeout(timeout),
      ),
      BleDeviceType.glucose => throw const UnsupportedBleDeviceException(
        '暂未支持血糖仪数据解析',
      ),
      BleDeviceType.weightScale => throw const UnsupportedBleDeviceException(
        '暂未支持体重秤数据解析',
      ),
      BleDeviceType.pulseOximeter => throw const UnsupportedBleDeviceException(
        '暂未支持血氧仪数据解析',
      ),
    };
  }

  Future<List<BluetoothService>> _connectAndDiscover(
    BluetoothDevice device,
  ) async {
    await _measurementSub?.cancel();
    _measurementSub = null;
    await _connStateSub?.cancel();
    _device = device;

    _connStateSub = device.connectionState.listen((state) {
      _connStateController.add(state == BluetoothConnectionState.connected);
    });

    if (device.isConnected) {
      try {
        await device.disconnect();
      } catch (_) {}
    }
    await device.connect(
      autoConnect: false,
      timeout: const Duration(seconds: 10),
    );
    try {
      await device.requestMtu(512);
    } catch (_) {
      // Some devices or platforms do not allow MTU negotiation.
    }
    final services = await device.discoverServices();
    return services;
  }

  Future<BpReading> _syncBloodPressure(
    List<BluetoothService> services, {
    VoidCallback? onMeasurementReceived,
  }) async {
    BluetoothService? bpService;
    for (final service in services) {
      if (service.uuid.str128.toUpperCase() ==
          BleDeviceIdentifier.bloodPressureServiceUuid) {
        bpService = service;
        break;
      }
    }
    if (bpService == null) {
      throw const UnsupportedBleDeviceException('未找到血压服务');
    }

    BluetoothCharacteristic? measurementChar;
    BluetoothCharacteristic? racpChar;
    for (final characteristic in bpService.characteristics) {
      final uuid = characteristic.uuid.str128.toUpperCase();
      if (uuid == bpMeasurementUuid) measurementChar = characteristic;
      if (uuid == racpUuid) racpChar = characteristic;
    }
    if (measurementChar == null) {
      throw const UnsupportedBleDeviceException('未找到血压测量特征');
    }

    final completer = Completer<BpReading>();
    final readings = <BpReading>[];
    Timer? settleTimer;

    void acceptReading(List<int> raw) {
      if (raw.isEmpty || completer.isCompleted) return;
      try {
        readings.add(_parseBloodPressureReading(raw));
        onMeasurementReceived?.call();
        settleTimer?.cancel();
        settleTimer = Timer(const Duration(milliseconds: 900), () {
          final reading = _selectCurrentBloodPressureReading(readings);
          if (!completer.isCompleted) {
            completer.complete(reading);
          }
        });
      } catch (e, stack) {
        if (!completer.isCompleted) completer.completeError(e, stack);
      }
    }

    _measurementSub = measurementChar.onValueReceived.listen(
      (raw) {
        acceptReading(raw);
      },
      onError: (Object e, StackTrace stack) {
        if (!completer.isCompleted) completer.completeError(e, stack);
      },
    );

    await _enableMeasurementUpdates(measurementChar);

    if (racpChar != null) {
      try {
        await racpChar.write([0x01, 0x01]);
      } catch (_) {}
    }

    try {
      return await completer.future;
    } finally {
      settleTimer?.cancel();
    }
  }

  BpReading _selectCurrentBloodPressureReading(List<BpReading> readings) {
    if (readings.isEmpty) {
      throw const FormatException('未收到血压数据');
    }

    final now = DateTime.now();
    final currentWindowStart = now.subtract(const Duration(minutes: 15));
    final currentWindowEnd = now.add(const Duration(minutes: 2));
    final timestampedCurrent = readings.where((reading) {
      if (!reading.hasDeviceTimestamp) return false;
      return !reading.timestamp.isBefore(currentWindowStart) &&
          !reading.timestamp.isAfter(currentWindowEnd);
    }).toList();

    if (timestampedCurrent.isNotEmpty) {
      timestampedCurrent.sort((a, b) => b.timestamp.compareTo(a.timestamp));
      return timestampedCurrent.first;
    }

    final timestamped = readings
        .where((reading) => reading.hasDeviceTimestamp)
        .toList();
    if (timestamped.isNotEmpty) {
      timestamped.sort((a, b) => b.timestamp.compareTo(a.timestamp));
      return timestamped.first;
    }

    return readings.last;
  }

  Future<void> _enableMeasurementUpdates(
    BluetoothCharacteristic characteristic,
  ) async {
    final useIndication = characteristic.properties.indicate;
    await characteristic.setNotifyValue(true, forceIndications: useIndication);
  }

  Future<void> disconnect() async {
    await _measurementSub?.cancel();
    _measurementSub = null;
    await _connStateSub?.cancel();
    _connStateSub = null;
    await _device?.disconnect();
    _device = null;
    _connStateController.add(false);
  }

  void dispose() {
    unawaited(disconnect());
    _connStateController.close();
  }

  BpReading _parseBloodPressureReading(List<int> raw) {
    if (raw.length < 7) {
      throw const FormatException('血压数据长度不足');
    }

    final flags = raw[0];
    final isKpa = (flags & 0x01) != 0;
    final hasTimestamp = (flags & 0x02) != 0;
    final hasPulse = (flags & 0x04) != 0;
    final hasUserId = (flags & 0x08) != 0;
    final hasStatus = (flags & 0x10) != 0;

    int systolic = _decodeSFloat(raw[1] | (raw[2] << 8)).round();
    int diastolic = _decodeSFloat(raw[3] | (raw[4] << 8)).round();
    if (isKpa) {
      systolic = (systolic * 7.50062).round();
      diastolic = (diastolic * 7.50062).round();
    }

    var offset = 7;
    var timestamp = DateTime.now();
    if (hasTimestamp && raw.length >= offset + 7) {
      timestamp = DateTime(
        raw[offset] | (raw[offset + 1] << 8),
        raw[offset + 2],
        raw[offset + 3],
        raw[offset + 4],
        raw[offset + 5],
        raw[offset + 6],
      );
      offset += 7;
    }

    int? pulse;
    if (hasPulse && raw.length >= offset + 2) {
      pulse = _decodeSFloat(raw[offset] | (raw[offset + 1] << 8)).round();
      offset += 2;
    }

    String? userId;
    if (hasUserId && raw.length > offset) {
      userId = raw[offset].toString();
      offset++;
    }

    var hasBodyMovement = false;
    var hasIrregularPulse = false;
    if (hasStatus && raw.length >= offset + 2) {
      final status = raw[offset] | (raw[offset + 1] << 8);
      hasBodyMovement = (status & 0x0001) != 0;
      hasIrregularPulse = (status & 0x0800) != 0;
    }

    return BpReading(
      systolic: systolic,
      diastolic: diastolic,
      pulse: pulse,
      timestamp: timestamp,
      userId: userId,
      isKpa: isKpa,
      hasBodyMovement: hasBodyMovement,
      hasIrregularPulse: hasIrregularPulse,
      hasDeviceTimestamp: hasTimestamp,
    );
  }

  static double _decodeSFloat(int raw) {
    var mantissa = raw & 0x0FFF;
    if (mantissa & 0x0800 != 0) mantissa |= 0xFFFFF000;
    var exponent = (raw >> 12) & 0x0F;
    if (exponent & 0x08 != 0) exponent |= 0xFFFFFFF0;
    return (mantissa * pow(10, exponent)).toDouble();
  }
}
