import 'dart:async';

import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_blue_plus_windows/flutter_blue_plus_windows.dart';
import 'package:uuid/uuid.dart';
import 'package:xiao_pet_tracker/bootstrap.dart';
import 'package:xiao_pet_tracker/objectbox.dart';
import 'package:xiao_pet_tracker/objectbox.g.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_box.dart';
import 'package:xiao_pet_tracker/xiao_connector/models/capture_point.dart';
import 'package:xiao_pet_tracker/xiao_connector/utils/utils.dart';

part 'xiao_connector_state.dart';

class XiaoConnectorCubit extends Cubit<XiaoConnectorState> {
  XiaoConnectorCubit() : super(const XiaoConnectorState());

  late BluetoothDevice device;
  List<BluetoothService> _services = [];
  late BluetoothCharacteristic _senseService;

  late StreamSubscription<List<int>> dataCapturingSubscription;

  final ObjectBox _objectBox = getIt<ObjectBox>();
  late final Box<CapturePoint> _capturePointsBox;
  late final Box<CaptureBox> _captureBoxes;

  bool gotRotation = false;
  bool gotAcceleration = false;
  bool gotTimeStamp = false;

  int _rotX = 0;
  int _rotY = 0;
  int _rotZ = 0;
  int _accX = 0;
  int _accY = 0;
  int _accZ = 0;
  int _millisecondsSinceEpochSend = 0;
  String _captureType = '';
  String _uuid = '';

  bool isRecording = false;

  Future<void> init() async {
    _capturePointsBox = _objectBox.store.box<CapturePoint>();
    _captureBoxes = _objectBox.store.box<CaptureBox>();
  }

  Future<void> connect() async {
    emit(state.copyWith(status: XiaoConnectorStatus.loading));

    try {
      // scan for devices for 15 seconds
      await FlutterBluePlus.startScan(timeout: const Duration(seconds: 15));
    } catch (e) {
      emit(state.copyWith(status: XiaoConnectorStatus.failure));
      throw Exception('Start Scan Error: $e');
    }

    FlutterBluePlus.scanResults.listen(
      (results) async {
        for (final r in results) {
          if (kDebugMode) {
            print(r);
          }

          // set the xiao sense name
          if (r.device.advName == 'Go Bluetooth') {
            await FlutterBluePlus.stopScan();
            device = r.device;

            await device.connect();

            try {
              _services = await device.discoverServices();
            } catch (e) {
              emit(state.copyWith(status: XiaoConnectorStatus.failure));
              throw Exception('Discover Services Error: $e');
            }

            device.connectionState
                .listen((BluetoothConnectionState blueState) async {
              if (blueState == BluetoothConnectionState.disconnected) {
                debugPrint('DISCONNECTED!');
                emit(state.copyWith(status: XiaoConnectorStatus.initial));
              }
            });

            // device.cancelWhenDisconnected(deviceConnectionStream);
            emit(state.copyWith(status: XiaoConnectorStatus.connected));
          }
        }
      },
      onDone: () {
        debugPrint('DONE');
        emit(state.copyWith(status: XiaoConnectorStatus.initial));
      },
    );
  }

  Future<void> setCapturingOn() async {
    final senseService = _services
        .where(
          (s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
        )
        .first
        .characteristics
        .where(
          (c) =>
              c.characteristicUuid ==
              Guid('63617074-7572-696E-6753-657276696365'),
        )
        .first;

    await senseService.write([1]);
  }

  Uint8List int64BigEndianBytes(int value) =>
      Uint8List(8)..buffer.asByteData().setInt64(0, value);

  Future<void> setTime(int millisSinceEpoch) async {
    final senseService = _services
        .where(
          (s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
        )
        .first
        .characteristics
        .where(
          (c) =>
              c.characteristicUuid ==
              Guid('756E6978-5469-6D65-5374-616D70527374'),
        )
        .first;

    final List<int> bytes = int64BigEndianBytes(millisSinceEpoch);
    await senseService.write(bytes);
  }

  Future<void> startCapturing() async {
    _senseService = _services
        .where(
          (s) => s.serviceUuid == Guid('4c534d36-4453-3354-5253-657276696365'),
        )
        .first
        .characteristics
        .where(
          (c) =>
              c.characteristicUuid ==
              Guid('61636365-6c65-7261-7469-6f6e44617461'),
        )
        .first;

    dataCapturingSubscription = _senseService.onValueReceived.listen((value) {
      // rotation
      if (value.last == 1) {
        _rotX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
        _rotY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
        _rotZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
        gotRotation = true;
      }

      // acceleration
      if (value.last == 2) {
        _accX = fromBytesToInt32(value[0], value[1], value[2], value[3]);
        _accY = fromBytesToInt32(value[4], value[5], value[6], value[7]);
        _accZ = fromBytesToInt32(value[8], value[9], value[10], value[11]);
        gotAcceleration = true;
      }

      if (value.last == 3) {
        final timeStamp = fromBytesToInt64(
          value[0],
          value[1],
          value[2],
          value[3],
          value[4],
          value[5],
          value[6],
          value[7],
        );
        _millisecondsSinceEpochSend = timeStamp;
        gotTimeStamp = true;
      }

      if (gotAcceleration && gotRotation && gotTimeStamp) {
        final capturePoint = CapturePoint(
          type: _captureType,
          uuid: _uuid,
          rotationX: _rotX,
          rotationY: _rotY,
          rotationZ: _rotZ,
          accelerationX: _accX,
          accelerationY: _accY,
          accelerationZ: _accZ,
          millisecondsSinceEpochReceived:
              DateTime.now().toUtc().millisecondsSinceEpoch,
          millisecondsSinceEpochSend: _millisecondsSinceEpochSend,
        );
        emit(state.copyWith(lastCapturedPoint: capturePoint));

        if (isRecording) {
          _writeToObjectBox(capturePoint: capturePoint);
        }

        gotAcceleration = false;
        gotRotation = false;
        gotTimeStamp = false;
      }
    });
    await setCapturingOn();

    await setTime(DateTime.now().millisecondsSinceEpoch);

    device.cancelWhenDisconnected(dataCapturingSubscription);

    await _senseService.setNotifyValue(true);

    emit(state.copyWith(status: XiaoConnectorStatus.capturing));
  }

  Future<void> stopCapturing() async {
    await dataCapturingSubscription.cancel();

    await _senseService.setNotifyValue(false);

    emit(state.copyWith(status: XiaoConnectorStatus.connected));
  }

  void toggleRecording({required String captureType}) {
    isRecording = !isRecording;
    if (isRecording) {
      _captureType = captureType;
      _uuid = const Uuid().v4();
      _captureBoxes.put(
        CaptureBox(
          type: _captureType,
          uuid: _uuid,
        ),
      );
    }
  }

  void _writeToObjectBox({
    required CapturePoint capturePoint,
  }) {
    _capturePointsBox.put(capturePoint);
  }

  List<CapturePoint> getCapturePointsOfUuid(String uuid) {
    final query =
        _capturePointsBox.query(CapturePoint_.uuid.equals(uuid)).build();

    final points = query.find();
    return points;
  }

  List<CaptureBox> getAllCaptureBoxes() => _captureBoxes.getAll();
  List<CapturePoint> getAllCapturePoints() => _capturePointsBox.getAll();
}
