import 'dart:async';
import 'dart:typed_data';

import 'package:flutter/foundation.dart';
import 'package:flutter_bluetooth_serial/flutter_bluetooth_serial.dart';

//import 'utils/converter.dart';
import '../serial/utils/converter.dart';
import 'utils/crc_calculator.dart';
import 'utils/parameter_helper.dart';

enum BluetoothConnectionState { disconnected, connecting, connected }

class BluetoothManager {
  Timer? _dataRequestTimer;

  final FlutterBluetoothSerial _bluetooth = FlutterBluetoothSerial.instance;
  BluetoothConnection? _connection;

  // --- State Notifiers ---
  final ValueNotifier<BluetoothConnectionState> connectionState =
  ValueNotifier(BluetoothConnectionState.disconnected);
  // MODIFIED: Changed to ValueNotifier for consistency and reactivity.
  final ValueNotifier<String?> connectedDeviceName = ValueNotifier(null);
  // ADDED: Notifier to hold the parsed Sonde ID.
  final ValueNotifier<String?> sondeId = ValueNotifier(null);

  final StreamController<Map<String, double>> _dataStreamController =
  StreamController<Map<String, double>>.broadcast();
  Stream<Map<String, double>> get dataStream => _dataStreamController.stream;

  // --- Internal State ---
  String? connectedDeviceAddress;
  int _runningCounter = 0;
  int _communicationLevel = 0;
  String? _parentAddress;
  final List<String> _parameterList = [];

  Future<List<BluetoothDevice>> getPairedDevices() async {
    try {
      return await _bluetooth.getBondedDevices();
    } catch (e) {
      print("Error getting paired devices: $e");
      return [];
    }
  }

  Future<void> connect(BluetoothDevice device) async {
    if (connectionState.value == BluetoothConnectionState.connected) return;
    try {
      connectionState.value = BluetoothConnectionState.connecting;
      _connection = await BluetoothConnection.toAddress(device.address);

      // MODIFIED: Set the .value of the notifier.
      connectedDeviceName.value = device.name;
      connectedDeviceAddress = device.address;

      connectionState.value = BluetoothConnectionState.connected;
      _connection!.input!.listen(_onDataReceived).onDone(disconnect);
    } catch (e) {
      print("Error connecting: $e");
      disconnect();
    }
  }

  void disconnect() {
    if (connectionState.value != BluetoothConnectionState.disconnected) {
      stopAutoReading(); // Ensure timer is stopped on disconnect
      _connection?.dispose();
      _connection = null;

      // MODIFIED: Reset the .value of the notifiers.
      connectedDeviceName.value = null;
      // ADDED: Reset Sonde ID on disconnect.
      sondeId.value = null;

      connectedDeviceAddress = null;
      connectionState.value = BluetoothConnectionState.disconnected;
    }
  }

  /// Starts a periodic timer that requests data automatically.
  void startAutoReading({Duration interval = const Duration(seconds: 2)}) {
    // Cancel any existing timer to prevent duplicates.
    stopAutoReading();

    // Request the first reading immediately without waiting for the timer.
    if (connectionState.value == BluetoothConnectionState.connected) {
      startLiveReading();
    }

    // Start a new timer that calls startLiveReading periodically.
    _dataRequestTimer = Timer.periodic(interval, (Timer t) {
      if (connectionState.value == BluetoothConnectionState.connected) {
        startLiveReading();
      } else {
        // If we get disconnected for any reason, stop the timer.
        stopAutoReading();
      }
    });
  }

  /// Stops the automatic data refresh timer.
  void stopAutoReading() {
    _dataRequestTimer?.cancel();
    _dataRequestTimer = null;
  }

  void startLiveReading() {
    if (connectionState.value != BluetoothConnectionState.connected) return;
    _communicationLevel = 0;
    _parameterList.clear();
    _parentAddress = null;
    _sendCommand(0);
  }

  void _onDataReceived(Uint8List data) {
    if (data.isEmpty) return;
    String responseHex = Converter.byteArrayToHexString(data).toUpperCase();
    print("Received (Lvl: $_communicationLevel): $responseHex");
    switch (_communicationLevel) {
      case 0:
        _handleResponseLevel0(responseHex);
        break;
      case 1:
        _handleResponseLevel1(responseHex);
        break;
      case 2:
        _handleResponseLevel2(responseHex);
        break;
    }
  }

  void _handleResponseLevel0(String responseHex) {
    try {
      if (responseHex.length < 94) {
        print("Level 0 response is too short.");
        return;
      }

      // ADDED: Parse Sonde ID (Serial Number) from the response.
      // This uses the same logic as your SerialManager.
      String serialHex = responseHex.substring(68, 86);
      String serialAscii = Converter.hexToAscii(serialHex);
      sondeId.value = serialAscii;
      print("Successfully Parsed Sonde ID: ${sondeId.value}");

      // Your existing logic to parse the Parent Address.
      final int dataBlockLength =
      int.parse(responseHex.substring(30, 34), radix: 16);
      if (dataBlockLength == 38) {
        const int dataBlockStart = 34;
        const int parentAddressOffset = 52;
        final int addressStart = dataBlockStart + parentAddressOffset;

        _parentAddress = responseHex.substring(addressStart, addressStart + 8);

        print("Successfully Parsed Parent Address: $_parentAddress");

        if (_parentAddress != "00000000") {
          _communicationLevel = 1;

          // Give the device a moment before we send the next command.
          Future.delayed(const Duration(milliseconds: 500)).then((_) {
            _sendCommand(1); // Move to next step
          });
        }
      }
    } catch (e) {
      print("Error parsing Level 0 response: $e");
      disconnect();
    }
  }

  void _handleResponseLevel1(String responseHex) {
    try {
      if (responseHex.length < 38) return;
      final int dataBlockLength =
      int.parse(responseHex.substring(30, 34), radix: 16);
      final String parametersDataBlock =
      responseHex.substring(34, 34 + (dataBlockLength * 2));
      _parameterList.clear();
      for (int i = 0; i <= parametersDataBlock.length - 6; i += 6) {
        String parameterCode = parametersDataBlock.substring(i + 2, i + 6);
        _parameterList.add(ParameterHelper.getDescription(parameterCode));
      }
      print("Parsed Parameters: $_parameterList");
      _communicationLevel = 2;

      // **** UPDATED CODE ****
      // Give the device a moment before requesting the final data.
      Future.delayed(const Duration(milliseconds: 500)).then((_) {
        _sendCommand(2); // Move to final step
      });
    } catch (e) {
      print("Error parsing Level 1 response: $e");
      disconnect();
    }
  }

  void _handleResponseLevel2(String responseHex) {
    try {
      if (responseHex.length < 38) return;
      final int dataBlockLength =
      int.parse(responseHex.substring(30, 34), radix: 16);
      final String valuesDataBlock =
      responseHex.substring(34, 34 + (dataBlockLength * 2));
      final List<double> parameterValues = [];
      for (int i = 0; i <= valuesDataBlock.length - 8; i += 8) {
        String valueHex = valuesDataBlock.substring(i, i + 8);
        parameterValues.add(Converter.hexToFloat(valueHex));
      }

      if (_parameterList.length == parameterValues.length) {
        Map<String, double> finalReadings = {};
        for (int i = 0; i < _parameterList.length; i++) {
          finalReadings[_parameterList[i]] = parameterValues[i];
        }
        print("Final Parsed Readings: $finalReadings");
        _dataStreamController.add(finalReadings);
      }
      // Reset for the next reading sequence
      _communicationLevel = 0;
    } catch (e) {
      print("Error parsing Level 2 response: $e");
      disconnect();
    }
  }

  void _sendCommand(int level) {
    if (_connection == null || !_connection!.isConnected) {
      print("Cannot send command, not connected.");
      return;
    }

    String commandHex;
    if (level == 0) {
      commandHex = _getCommand0();
    } else if (level == 1) {
      commandHex = _getCommand1();
    } else {
      commandHex = _getCommand2();
    }

    Uint8List commandBytes = Converter.hexStringToByteArray(commandHex);
    String crcHexString = computeCrc16Ccitt(commandBytes);
    String finalHexPacket = commandHex + crcHexString;
    Uint8List packetToSend = Converter.hexStringToByteArray(finalHexPacket);

    try {
      _connection?.output.add(packetToSend);
      _connection?.output.allSent.then((_) {
        print("Sent (Lvl: $level): $finalHexPacket");
      });
    } catch (e) {
      print("Error sending data: $e");
      disconnect();
    }
  }

  String _getCommand0() {
    String seqNo = (_runningCounter++ & 255).toRadixString(16).padLeft(2, '0');
    return '7E02${seqNo}0000000002000000200000010000';
  }

  String _getCommand1() {
    String seqNo = (_runningCounter++ & 255).toRadixString(16).padLeft(2, '0');
    return '7E02$seqNo${_parentAddress}02000000200000180000';
  }

  String _getCommand2() {
    String seqNo = (_runningCounter++ & 255).toRadixString(16).padLeft(2, '0');
    return '7E02$seqNo${_parentAddress}02000000200000190000';
  }

  void dispose() {
    disconnect();
    _dataStreamController.close();
    connectionState.dispose();
    // ADDED: Dispose of the new notifiers to prevent memory leaks.
    connectedDeviceName.dispose();
    sondeId.dispose();
  }
}