import 'package:flutter/material.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';

import 'bp_reading.dart';

enum BleDeviceType { bloodPressure, glucose, weightScale, pulseOximeter }

enum SupportedMetric { bloodPressure, heartRate, glucose, weight, spo2 }

extension BleDeviceTypeX on BleDeviceType {
  String get code => switch (this) {
    BleDeviceType.bloodPressure => 'bloodPressure',
    BleDeviceType.glucose => 'glucose',
    BleDeviceType.weightScale => 'weightScale',
    BleDeviceType.pulseOximeter => 'pulseOximeter',
  };

  String get label => switch (this) {
    BleDeviceType.bloodPressure => '血压计',
    BleDeviceType.glucose => '血糖仪',
    BleDeviceType.weightScale => '体重秤',
    BleDeviceType.pulseOximeter => '血氧仪',
  };

  String get serviceUuid => switch (this) {
    BleDeviceType.bloodPressure => BleDeviceIdentifier.bloodPressureServiceUuid,
    BleDeviceType.glucose => BleDeviceIdentifier.glucoseServiceUuid,
    BleDeviceType.weightScale => BleDeviceIdentifier.weightScaleServiceUuid,
    BleDeviceType.pulseOximeter => BleDeviceIdentifier.pulseOximeterServiceUuid,
  };

  List<SupportedMetric> get metrics => switch (this) {
    BleDeviceType.bloodPressure => [
      SupportedMetric.bloodPressure,
      SupportedMetric.heartRate,
    ],
    BleDeviceType.glucose => [SupportedMetric.glucose],
    BleDeviceType.weightScale => [SupportedMetric.weight],
    BleDeviceType.pulseOximeter => [
      SupportedMetric.spo2,
      SupportedMetric.heartRate,
    ],
  };

  IconData get icon => switch (this) {
    BleDeviceType.bloodPressure => Icons.speed_rounded,
    BleDeviceType.glucose => Icons.water_drop_rounded,
    BleDeviceType.weightScale => Icons.monitor_weight_outlined,
    BleDeviceType.pulseOximeter => Icons.air_rounded,
  };

  static BleDeviceType? fromCode(String? code) {
    for (final type in BleDeviceType.values) {
      if (type.code == code) return type;
    }
    return null;
  }
}

extension SupportedMetricX on SupportedMetric {
  String get code => switch (this) {
    SupportedMetric.bloodPressure => 'BloodPressure',
    SupportedMetric.heartRate => 'HeartRate',
    SupportedMetric.glucose => 'Glucose',
    SupportedMetric.weight => 'Weight',
    SupportedMetric.spo2 => 'SpO2',
  };

  String get label => switch (this) {
    SupportedMetric.bloodPressure => '血压',
    SupportedMetric.heartRate => '心率',
    SupportedMetric.glucose => '血糖',
    SupportedMetric.weight => '体重',
    SupportedMetric.spo2 => '血氧',
  };
}

class BoundBleDevice {
  final String id;
  final String name;
  final BleDeviceType type;
  final String serviceUuid;
  final DateTime? lastSyncAt;

  const BoundBleDevice({
    required this.id,
    required this.name,
    required this.type,
    required this.serviceUuid,
    this.lastSyncAt,
  });

  List<SupportedMetric> get metrics => type.metrics;

  BoundBleDevice copyWith({
    String? id,
    String? name,
    BleDeviceType? type,
    String? serviceUuid,
    DateTime? lastSyncAt,
  }) {
    return BoundBleDevice(
      id: id ?? this.id,
      name: name ?? this.name,
      type: type ?? this.type,
      serviceUuid: serviceUuid ?? this.serviceUuid,
      lastSyncAt: lastSyncAt ?? this.lastSyncAt,
    );
  }

  Map<String, dynamic> toJson() => {
    'id': id,
    'name': name,
    'type': type.code,
    'serviceUuid': serviceUuid,
    'lastSyncAt': lastSyncAt?.toUtc().toIso8601String(),
  };

  static BoundBleDevice? fromJson(Map<String, dynamic> json) {
    final type = BleDeviceTypeX.fromCode(json['type']?.toString());
    final id = json['id']?.toString();
    if (type == null || id == null || id.isEmpty) return null;

    final lastSyncRaw = json['lastSyncAt']?.toString();
    return BoundBleDevice(
      id: id,
      name: json['name']?.toString() ?? type.label,
      type: type,
      serviceUuid: json['serviceUuid']?.toString() ?? type.serviceUuid,
      lastSyncAt: lastSyncRaw == null || lastSyncRaw.isEmpty
          ? null
          : DateTime.tryParse(lastSyncRaw)?.toLocal(),
    );
  }
}

class BleDeviceIdentifier {
  static const glucoseServiceUuid = '00001808-0000-1000-8000-00805F9B34FB';
  static const bloodPressureServiceUuid =
      '00001810-0000-1000-8000-00805F9B34FB';
  static const weightScaleServiceUuid = '0000181D-0000-1000-8000-00805F9B34FB';
  static const pulseOximeterServiceUuid =
      '00001822-0000-1000-8000-00805F9B34FB';

  static BleDeviceType? typeFromScan(ScanResult result) {
    return typeFromUuidStrings(
      result.advertisementData.serviceUuids.map((uuid) => uuid.str128),
    );
  }

  static BleDeviceType? typeFromServices(List<BluetoothService> services) {
    return typeFromUuidStrings(services.map((service) => service.uuid.str128));
  }

  static BleDeviceType? typeFromUuidStrings(Iterable<String> uuids) {
    final normalized = uuids.map((uuid) => uuid.toUpperCase()).toSet();
    if (normalized.contains(bloodPressureServiceUuid)) {
      return BleDeviceType.bloodPressure;
    }
    if (normalized.contains(glucoseServiceUuid)) {
      return BleDeviceType.glucose;
    }
    if (normalized.contains(weightScaleServiceUuid)) {
      return BleDeviceType.weightScale;
    }
    if (normalized.contains(pulseOximeterServiceUuid)) {
      return BleDeviceType.pulseOximeter;
    }
    return null;
  }

  static bool isSupportedScanResult(ScanResult result) {
    return typeFromScan(result) != null;
  }
}

sealed class HealthBleSyncResult {
  const HealthBleSyncResult({required this.device});

  final BoundBleDevice device;
}

class BloodPressureSyncResult extends HealthBleSyncResult {
  const BloodPressureSyncResult({required super.device, required this.reading});

  final BpReading reading;
}
