import 'dart:async';
import 'dart:convert';

import 'package:flutter_blue_plus/flutter_blue_plus.dart';

import '../models/ble_models.dart';

class BleService {
  static final Guid _provisionServiceUuid =
      Guid('12345678-1234-5678-1234-56789abcdef0');
  static final Guid _ssidCharacteristicUuid =
      Guid('12345678-1234-5678-1234-56789abcdef1');
  static final Guid _passwordCharacteristicUuid =
      Guid('12345678-1234-5678-1234-56789abcdef2');
  static final Guid _commandCharacteristicUuid =
      Guid('12345678-1234-5678-1234-56789abcdef3');
  static final Guid _statusCharacteristicUuid =
      Guid('12345678-1234-5678-1234-56789abcdef4');

  BluetoothDevice? _connectedDevice;
  BluetoothCharacteristic? _ssidCharacteristic;
  BluetoothCharacteristic? _passwordCharacteristic;
  BluetoothCharacteristic? _commandCharacteristic;
  BluetoothCharacteristic? _statusCharacteristic;
  StreamSubscription<List<ScanResult>>? _scanSubscription;

  Stream<List<BleDevice>> scanForShowenDevices({
    Duration timeout = const Duration(seconds: 6),
  }) {
    final controller = StreamController<List<BleDevice>>();
    final seen = <String, BleDevice>{};

    unawaited(_scanSubscription?.cancel());
    _scanSubscription = FlutterBluePlus.scanResults.listen((results) {
      for (final result in results) {
        final name = _deviceName(result);
        if (name.isEmpty || !name.toLowerCase().contains('showen')) {
          continue;
        }
        final id = result.device.remoteId.str;
        seen[id] = BleDevice(name: name, id: id, rssi: result.rssi);
      }

      final devices = seen.values.toList(growable: false)
        ..sort((a, b) => b.rssi.compareTo(a.rssi));
      if (!controller.isClosed) {
        controller.add(devices);
      }
    });

    unawaited(FlutterBluePlus.startScan(timeout: timeout));
    Future<void>.delayed(timeout, () async {
      await FlutterBluePlus.stopScan();
      if (!controller.isClosed) {
        await controller.close();
      }
    });

    controller.onCancel = () async {
      await FlutterBluePlus.stopScan();
      await _scanSubscription?.cancel();
      _scanSubscription = null;
    };

    return controller.stream;
  }

  Future<void> connectToDevice(BleDevice device) async {
    await disconnect();
    final bluetoothDevice = BluetoothDevice.fromId(device.id);
    await bluetoothDevice.connect(timeout: const Duration(seconds: 12));
    _connectedDevice = bluetoothDevice;
    await _discoverCharacteristics(bluetoothDevice);
  }

  Future<Stream<BleStatus>> subscribeToStatus() async {
    final characteristic = _statusCharacteristic;
    if (characteristic == null) {
      return Stream<BleStatus>.value(
        const BleStatus(
          ok: false,
          action: 'status',
          error: '未发现 BLE 状态特征值',
        ),
      );
    }

    await characteristic.setNotifyValue(true);
    return characteristic.lastValueStream
        .where((value) => value.isNotEmpty)
        .map((value) {
      final raw = utf8.decode(value, allowMalformed: true);
      try {
        return BleStatus.fromRawJson(raw);
      } catch (_) {
        return BleStatus(
          ok: true,
          action: 'status',
          state: raw,
        );
      }
    });
  }

  Future<BleStatus> provisionWifi(
    String ssid,
    String password, {
    Duration timeout = const Duration(seconds: 30),
  }) async {
    final ssidChar = _ssidCharacteristic;
    final passwordChar = _passwordCharacteristic;
    final command = _commandCharacteristic;

    if (_connectedDevice == null) {
      throw StateError('未连接 BLE 设备');
    }
    if (ssidChar == null || passwordChar == null || command == null) {
      throw StateError('未发现 BLE 配网特征值');
    }

    final statusStream = await subscribeToStatus();
    final completer = Completer<BleStatus>();
    late final StreamSubscription<BleStatus> subscription;
    subscription = statusStream.listen((status) {
      if (!status.isQueued && !completer.isCompleted) {
        completer.complete(status);
      }
    }, onError: (Object error, StackTrace stackTrace) {
      if (!completer.isCompleted) {
        completer.completeError(error, stackTrace);
      }
    });

    await ssidChar.write(
      utf8.encode(ssid),
      withoutResponse: ssidChar.properties.writeWithoutResponse,
    );
    await passwordChar.write(
      utf8.encode(password),
      withoutResponse: passwordChar.properties.writeWithoutResponse,
    );
    await command.write(
      utf8.encode('connect'),
      withoutResponse: command.properties.writeWithoutResponse,
    );

    try {
      return await completer.future.timeout(timeout);
    } finally {
      await subscription.cancel();
    }
  }

  Future<void> sendCommand(String command) async {
    final characteristic = _commandCharacteristic;
    if (_connectedDevice == null) {
      throw StateError('未连接 BLE 设备');
    }
    if (characteristic == null) {
      throw StateError('未发现 BLE 命令特征值');
    }

    await characteristic.write(
      utf8.encode(command),
      withoutResponse: characteristic.properties.writeWithoutResponse,
    );
  }

  Future<void> play() => sendCommand('play');

  Future<void> pause() => sendCommand('pause');

  Future<void> next() => sendCommand('next');

  Future<void> previous() => sendCommand('prev');

  Future<void> disconnect() async {
    await _scanSubscription?.cancel();
    _scanSubscription = null;

    final device = _connectedDevice;
    _connectedDevice = null;
    _ssidCharacteristic = null;
    _passwordCharacteristic = null;
    _commandCharacteristic = null;
    _statusCharacteristic = null;

    if (device != null) {
      await device.disconnect();
    }
  }

  Future<void> dispose() => disconnect();

  Future<void> _discoverCharacteristics(BluetoothDevice device) async {
    final services = await device.discoverServices();

    BluetoothCharacteristic? ssid;
    BluetoothCharacteristic? password;
    BluetoothCharacteristic? command;
    BluetoothCharacteristic? status;

    for (final service in services) {
      final shouldInspect = service.uuid == _provisionServiceUuid ||
          services.length == 1 ||
          service.characteristics.isNotEmpty;
      if (!shouldInspect) {
        continue;
      }

      for (final characteristic in service.characteristics) {
        if (characteristic.uuid == _ssidCharacteristicUuid) {
          ssid = characteristic;
        } else if (characteristic.uuid == _passwordCharacteristicUuid) {
          password = characteristic;
        } else if (characteristic.uuid == _commandCharacteristicUuid) {
          command = characteristic;
        } else if (characteristic.uuid == _statusCharacteristicUuid) {
          status = characteristic;
        }
      }
    }

    _ssidCharacteristic = ssid;
    _passwordCharacteristic = password;
    _commandCharacteristic = command;
    _statusCharacteristic = status;
  }

  String _deviceName(ScanResult result) {
    final platformName = result.device.platformName.trim();
    if (platformName.isNotEmpty) {
      return platformName;
    }
    final advertisedName = result.advertisementData.advName.trim();
    return advertisedName;
  }
}
