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

import 'package:geolocator/geolocator.dart';
import 'package:get/get.dart';
import 'package:nmea/nmea.dart';
import 'package:terepi_seged/gnss_sentences/gngsa.dart';
import 'package:terepi_seged/gnss_sentences/gngsv.dart';
import 'package:terepi_seged/models/satellite_info.dart';
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
import 'package:terepi_seged/services/gnss/phone_gps_connection.dart';

import '../../gnss_sentences/gngga.dart';
import '../../gnss_sentences/gngst.dart';
import '../../gnss_sentences/gnrmc.dart';
import 'bt_serial_gnss_connection.dart';
import 'ble_gnss_connection.dart';
import 'gnss_connection.dart';

class GnssService extends GetxService {
  static GnssService get to => Get.find();

  GnssConnection? _connection;

  // ── Kapcsolat állapot ─────────────────────────────────────────────
  final connectionState = GnssConnectionState.disconnected.obs;
  final activeConnectionType = Rxn<GnssConnectionType>();

  final Map<String, List<SatelliteInfo>> _gsvBuffer = {};
  final Map<String, DateTime> _gsvUpdatedAt = {};
  static const Duration _gsvTtl = Duration(seconds: 4);

  final Map<String, List<int>> _gsaPrnBuffer = {};
  final Map<String, DateTime> _gsaUpdatedAt = {};
  static const Duration _gsaTtl = Duration(seconds: 4);

  final activePrns = <int>[].obs;
  final activeSatelliteKeys = <String>[].obs;

  // ── GGA adatok ────────────────────────────────────────────────────
  final latitude = 0.0.obs;
  final longitude = 0.0.obs;
  final altitude = 0.0.obs; // MSL (ortometrikus)
  final geoidSeparation = 0.0.obs; // N — geoid undulációja
  final gpsQuality = 0.obs;
  final utcFix = ''.obs;
  final satelliteCount = 0.obs;
  final gsaUsedSatelliteCount = 0.obs;

  final hdop = 0.0.obs;
  final pdop = 0.0.obs;
  final vdop = 0.0.obs;

  // Utolsó nyers GGA sor — NtripService küldi vissza a casternek
  final lastGgaLine = ''.obs;

  // ── GST adatok (pontossági hibák) ─────────────────────────────────
  final latitudeError = 0.0.obs;
  final longitudeError = 0.0.obs;
  final altitudeError = 0.0.obs;

  // ── RMC adatok (dátum/idő) ────────────────────────────────────────
  final gpsDateTime = DateTime(2000).obs;

  final satellites = <SatelliteInfo>[].obs;
  final fixType = 0.obs;

  Timer? _reconnectTimer;
  bool _isClosing = false;

  // Segédmező: van-e érvényes adat
  bool get hasValidData => gpsQuality.value > 0;

  // Számított DOP minősítés (segéd getter-ek)
  String get hdopRating => _dopRating(hdop.value);
  String get vdopRating => _dopRating(vdop.value);
  String get pdopRating => _dopRating(pdop.value);

  String _dopRating(double dop) {
    if (dop <= 0) return '–';
    if (dop <= 1) return 'Ideális';
    if (dop <= 2) return 'Kiváló';
    if (dop <= 5) return 'Jó';
    if (dop <= 10) return 'Közepes';
    if (dop <= 20) return 'Gyenge';
    return 'Rossz';
  }

  int get totalVisibleSatellites => satellites.length;
  int get totalUsedSatellites => gsaUsedSatelliteCount.value;
  bool isSatelliteUsed(int prn) => activePrns.contains(prn);
  bool isSatelliteUsedSatellite(SatelliteInfo sat) {
    return activeSatelliteKeys.contains(sat.satelliteKey);
  }

  double? get horizontalAccuracy {
    final lat = latitudeError.value;
    final lon = longitudeError.value;

    return sqrt(lat * lat + lon * lon);
  }

  // ── Belső ─────────────────────────────────────────────────────────
  final NmeaDecoder _decoder = NmeaDecoder();
  StreamSubscription? _nmeaSub;
  StreamSubscription? _stateSub;
  StreamSubscription<Position>? _positionSub;

  final _updateController = StreamController<void>.broadcast();
  Stream<void> get onDataUpdated => _updateController.stream;

  String _utcTime = '';
  String _utcDate = '';

  bool _intentionalDisconnection = false;

  String _digitsOnly(String value) => value.replaceAll(RegExp(r'[^0-9]'), '');

  @override
  void onInit() {
    super.onInit();
    _decoder
      ..registerTalkerSentence('GGA', (l) => Gngga(raw: l))
      ..registerTalkerSentence('GST', (l) => Gngst(raw: l))
      ..registerTalkerSentence('RMC', (l) => Gnrmc(raw: l))
      ..registerTalkerSentence('GSA', (l) => Gngsa(raw: l))
      ..registerTalkerSentence('GSV', (l) => Gngsv(raw: l));
  }

  // ── Kapcsolódás ───────────────────────────────────────────────────

  Future<void> connectBtSerial(String macAddress) async {
    await _disconnect();
    _connection = BtSerialGnssConnection();
    activeConnectionType.value = GnssConnectionType.btSerial;
    await _doConnect(macAddress);
  }

  Future<void> connectBle(
    String deviceId, {
    String? serviceUuid,
    String? txCharUuid,
  }) async {
    await _disconnect();
    _connection = BleGnssConnection(
      serviceUuid: serviceUuid ?? '6e400001-b5b3-f393-e0a9-e50e24dcca9e',
      txCharUuid: txCharUuid ?? '6e400003-b5b3-f393-e0a9-e50e24dcca9e',
    );
    activeConnectionType.value = GnssConnectionType.ble;
    await _doConnect(deviceId);
  }

  /// Eszközváltás — GnssDevicePicker hívja.
  Future<void> onDeviceChanged(GnssDevice? device) async {
    if (device == null) {
      await _disconnect();
      activeConnectionType.value = GnssConnectionType.none;
      return;
    }

    switch (device.type) {
      case GnssConnectionType.none:
        await _disconnect();
        activeConnectionType.value = GnssConnectionType.none;
        break;
      case GnssConnectionType.btSerial:
        await connectBtSerial(device.address);
        break;
      case GnssConnectionType.ble:
        await connectBle(device.address);
        break;
      case GnssConnectionType.phoneGps:
        await _disconnect();
        _connection = PhoneGpsConnection();
        //connectionState.value = GnssConnectionState.disconnected;
        activeConnectionType.value = GnssConnectionType.phoneGps;
        await _doConnect('iternal');
        break;
    }
  }

  Future<void> reconnect() async {
    final device = GnssDeviceService.to.selectedDevice.value;
    if (device == null) return;
    await _disconnect();
    await onDeviceChanged(device);
  }

  Future<void> _doConnect(String address) async {
    _stateSub = _connection!.connectionState.listen((s) {
      connectionState.value = s;
      if (s == GnssConnectionState.connected) {
        _reconnectTimer?.cancel();
        return;
      }

      if (s == GnssConnectionState.disconnected) {
        _scheduleReconnect();
      }
    });

    _nmeaSub = _connection!.nmeaLines.listen(_parseNmea);
    _positionSub = _connection!.positionStream.listen(_parseDirectPosition);

    _intentionalDisconnection = false;
    await _connection!.connect(address);
  }

  void _parseDirectPosition(Position pos) {
    if (_isClosing) return;

    latitude.value = pos.latitude;
    longitude.value = pos.longitude;
    altitude.value = pos.altitude;

    gpsQuality.value = 1; // 1 = Standard (nem-RTK) minőség
    satelliteCount.value = 0; // A Geolocator nem ad műholdszámot direktben

    // A Geolocator a pontosságot (accuracy) méterben adja vissza
    latitudeError.value = pos.accuracy;
    longitudeError.value = pos.accuracy;
    altitudeError.value = pos.altitudeAccuracy;

    // Az RMC (idő) adatait is beállítjuk a telefon idejéből
    gpsDateTime.value = pos.timestamp;

    _emitUpdate();
  }

  Future<void> _disconnect() async {
    _intentionalDisconnection = true;
    _reconnectTimer?.cancel();

    await _nmeaSub?.cancel();
    await _positionSub?.cancel();
    await _stateSub?.cancel();

    await _connection?.disconnect();
    _connection?.dispose();
    _connection = null;
    connectionState.value = GnssConnectionState.disconnected;

    _gsaPrnBuffer.clear();
    _gsaUpdatedAt.clear();
    _gsvBuffer.clear();
    _gsvUpdatedAt.clear();
    activePrns.clear();
    activeSatelliteKeys.clear();
    satellites.clear();
    satelliteCount.value = 0;
    gsaUsedSatelliteCount.value = 0;
    pdop.value = 0;
    hdop.value = 0;
    vdop.value = 0;
    fixType.value = 0;
    lastGgaLine.value = '';
    gpsQuality.value = 0;
  }

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

  /// RTCM adat továbbítása a GNSS vevőnek (NtripService hívja).
  void sendToReceiver(Uint8List data) {
    if (_connection == null) return;
    if (connectionState.value != GnssConnectionState.connected) return;
    _connection?.sendData(data);
  }

  // ── NMEA parsing ──────────────────────────────────────────────────

  void _parseNmea(String line) {
    if (_isClosing || line.length < 6 || !line.startsWith(r'$')) return;

    final sentenceType = line.substring(3, 6);

    if (sentenceType == 'GGA') {
      _parseGga(line);
    } else if (sentenceType == 'GST' && hasValidData) {
      _parseGst(line);
    } else if (sentenceType == 'RMC' && hasValidData) {
      _parseRmc(line);
    } else if (sentenceType == 'GSA' && hasValidData) {
      _parseGsa(line);
    } else if (sentenceType == 'GSV' && hasValidData) {
      _parseGsv(line);
    }
  }

  void _parseGga(String line) {
    try {
      final s = _decoder.decode(line);
      if (s == null || !s.valid || s is! Gngga) return;
      if (s.gpsQualityIndicator == 0) return;

      latitude.value = s.latitude;
      longitude.value = s.longitude;
      altitude.value = s.altitudeAboveMeanSeaLevel;
      geoidSeparation.value = s.geoidSeparation;
      gpsQuality.value = s.gpsQualityIndicator;
      utcFix.value = s.utcOfPositionFix;
      // A GGA altal riportalt, fixben hasznalt muholdszam.
      satelliteCount.value = s.numberOfSvsInUse;
      //hdop.value = s.hdop;
      lastGgaLine.value = line;
      _utcTime = s.utcOfPositionFix;

      _emitUpdate();
    } catch (e) {
      print('GGA parsing error: $e');
    }
  }

  void _parseGst(String line) {
    try {
      final s = _decoder.decode(line);
      if (s == null || !s.valid || s is! Gngst) return;
      latitudeError.value = s.latitudeError;
      longitudeError.value = s.longitudeError;
      altitudeError.value = s.heightError;
    } catch (e) {
      print('GST parse error: $e');
    }
  }

  void _parseRmc(String line) {
    try {
      final s = _decoder.decode(line);
      if (s == null || !s.valid || s is! Gnrmc) return;

      _utcDate = s.date;
      final date = _digitsOnly(_utcDate);
      final time = _digitsOnly(s.utcOfPositionFix);

      if (date.length >= 6 && time.length >= 6) {
        gpsDateTime.value = DateTime.utc(
          2000 + int.parse(date.substring(4, 6)),
          int.parse(date.substring(2, 4)),
          int.parse(date.substring(0, 2)),
          int.parse(time.substring(0, 2)),
          int.parse(time.substring(2, 4)),
          int.parse(time.substring(4, 6)),
        );
      }
    } catch (e) {
      print('RMC parse error: $e');
    }
  }

  void _parseGsa(String line) {
    try {
      final s = _decoder.decode(line);
      if (s == null || !s.valid || s is! Gngsa) return;

      final key = s.constellationKey;
      _gsaPrnBuffer[key] = s.activeSatellitePrns;
      _gsaUpdatedAt[key] = DateTime.now().toUtc();
      _purgeStaleGsa();

      final satKeys = <String>{};
      for (final entry in _gsaPrnBuffer.entries) {
        for (final prn in entry.value) {
          satKeys.add('${entry.key}:$prn');
        }
      }

      activeSatelliteKeys.assignAll(satKeys);
      activePrns
          .assignAll(satKeys.map((k) => int.parse(k.split(':').last)).toSet());

      gsaUsedSatelliteCount.value = activeSatelliteKeys.length;

      if (s.pdop > 0 && (pdop.value == 0 || s.pdop <= pdop.value)) {
        pdop.value = s.pdop;
        hdop.value = s.hdop;
        vdop.value = s.vdop;
        fixType.value = s.fixType;
      }
    } catch (e) {
      print('GSA parse error: $e');
    }
  }

  void _parseGsv(String line) {
    try {
      final s = _decoder.decode(line);
      if (s == null || !s.valid || s is! Gngsv) return;

      final streamKey = s.streamKey;

      if (s.messageNumber == 1) {
        _gsvBuffer[streamKey] = <SatelliteInfo>[];
      }

      _gsvBuffer[streamKey] = [
        ...?_gsvBuffer[streamKey],
        ...s.satellites,
      ];

      _gsvUpdatedAt[streamKey] = DateTime.now().toUtc();
      _purgeStaleGsv();

      if (s.isLastMessage) {
        _updateSatelliteList();
      }
    } catch (e) {
      print('GSV parse error: $e');
    }
  }

  /// Összes rendszer pufferéből összerakja a teljes műholdlistát.
  /// Akkor hívódik, amikor egy rendszer utolsó GSV mondata megérkezett.
  void _updateSatelliteList() {
    _purgeStaleGsv();

    final allSats = _gsvBuffer.values.expand((list) => list).toList();
    if (allSats.isEmpty) return;

    final bySatellite = <String, SatelliteInfo>{};

    for (final sat in allSats) {
      final key = sat.satelliteKey;
      final prev = bySatellite[key];

      // Ugyanaz a műhold tobb signal stream-ben is johet.
      // A legerősebb SNR-es bejegyzest tartjuk meg.
      if (prev == null || sat.snr > prev.snr) {
        bySatellite[key] = sat;
      }
    }

    satellites.assignAll(bySatellite.values);
  }

  @override
  void onClose() async {
    _isClosing = true;

    await _disconnect();

    if (!_updateController.isClosed) {
      _updateController.close();
    }

    super.onClose();
  }

  Future<void> determineInitialPosition() async {
    // 1. Ha már van élő adatunk (pl. a külső vevő már küldött koordinátát),
    // akkor nincs szükség extra lekérdezésre.
    if (latitude.value != 0 && longitude.value != 0) return;

    try {
      // 2. Gyors lekérdezés: megkérdezzük a telefont, hol voltunk utoljára.
      // Ez szinte azonnal visszatér, nem pörgeti fel a GPS chipet.
      final lastPosition = await Geolocator.getLastKnownPosition();

      if (lastPosition != null) {
        latitude.value = lastPosition.latitude;
        longitude.value = lastPosition.longitude;
        return;
      }

      // 3. Ha nincs utolsó ismert pozíció (pl. friss telepítés),
      // kérünk egy friss pozíciót, de alacsony pontossággal, hogy gyors legyen.
      final currentPosition = await Geolocator.getCurrentPosition(
        desiredAccuracy: LocationAccuracy.low,
        timeLimit: const Duration(seconds: 3), // Ne akassza meg az appot sokáig
      );

      latitude.value = currentPosition.latitude;
      longitude.value = currentPosition.longitude;
    } catch (e) {
      // Engedélyhiány vagy kikapcsolt helymeghatározás esetén a térkép
      // marad a (0,0)-n vagy egy alapértelmezett (pl. budapesti) koordinátán.
      print('Nem sikerült lekérni a kezdőpozíciót: $e');
    }
  }

  void _emitUpdate() {
    if (_isClosing || _updateController.isClosed) return;
    _updateController.add(null);
  }

  void _scheduleReconnect() {
    if (_intentionalDisconnection || _isClosing) return;
    if (_reconnectTimer?.isActive ?? false) return;

    _reconnectTimer = Timer(const Duration(seconds: 3), () {
      if (_intentionalDisconnection || _isClosing) return;
      unawaited(reconnect());
    });
  }

  void _purgeStaleGsa() {
    final cutoff = DateTime.now().toUtc().subtract(_gsaTtl);

    final staleKeys = _gsaUpdatedAt.entries
        .where((e) => e.value.isBefore(cutoff))
        .map((e) => e.key)
        .toList(growable: false);

    for (final key in staleKeys) {
      _gsaUpdatedAt.remove(key);
      _gsaPrnBuffer.remove(key);
    }
  }

  void _purgeStaleGsv() {
    final cutoff = DateTime.now().toUtc().subtract(_gsvTtl);

    final staleKeys = _gsvUpdatedAt.entries
        .where((e) => e.value.isBefore(cutoff))
        .map((e) => e.key)
        .toList(growable: false);

    for (final key in staleKeys) {
      _gsvUpdatedAt.remove(key);
      _gsvBuffer.remove(key);
    }
  }
}
