// lib/services/ntrip_service.dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';

import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:terepi_seged/services/gnss/gnss_connection.dart';
import 'package:terepi_seged/services/gnss/gnss_device_service.dart';
import 'package:terepi_seged/services/gnss/gnss_service.dart';

class NtripException implements Exception {
  final String message;
  NtripException(this.message);

  @override
  String toString() => message;
}

/// NTRIP kapcsolatot kezelő singleton service.
///
/// Felelőssége:
///  - Socket kapcsolat az NTRIP casterhez
///  - RTCM adatok fogadása és továbbítása a GNSS vevőnek
///  - GGA mondatok küldése a casternek (5 másodpercenként)
///  - Beállítások tárolása SharedPreferences-ben

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

  // ── Reaktív állapot ───────────────────────────────────────────────
  final isConnected = false.obs;
  final receivedBytes = 0.obs;
  final packetCount = 0.obs;
  final ggaSentCount = 0.obs;
  final ggaLastSentTime = ''.obs;
  final lastError = ''.obs;

  final lastRtcmAt = Rxn<DateTime>();
  final connectedSince = Rxn<DateTime>();

  // ── Beállítások ───────────────────────────────────────────────────
  final host = ''.obs; // gnssnet.hu IP
  final port = 2101.obs;
  final mountpoint = ''.obs;
  final username = ''.obs;
  final password = ''.obs;
  final autoConnect = false.obs;

  bool get hasCompleteSettings =>
      host.value.trim().isNotEmpty && mountpoint.value.trim().isNotEmpty;

  // // ── UI controllerek (beállítás dialóghoz) ────────────────────────
  // final hostController = TextEditingController();
  // final portController = TextEditingController();
  // final mountpointController = TextEditingController();
  // final usernameController = TextEditingController();
  // final passwordController = TextEditingController();

  // ── Belső állapot ────────────────────────────────────────────────
  static const _secure = FlutterSecureStorage();

  Socket? _socket;
  StreamSubscription? _socketSub;
  String _lastGgaMessage = '';
  DateTime _lastGgaSentTime =
      DateTime.now().subtract(const Duration(seconds: 30));

  bool _headerValidated = false;
  final List<int> _headerBytes = [];
  Completer<void>? _connectCompleter;

  Function(Uint8List)? onRtcmData;

  // ── Inicializálás ────────────────────────────────────────────────

  @override
  Future<void> onInit() async {
    super.onInit();
    await _loadSettings();
    //  _syncControllersFromValues();
    _wireGnssListener();
  }

  void _wireGnssListener() {
    if (!Get.isRegistered<GnssService>()) {
      Future.delayed(const Duration(seconds: 2), _wireGnssListener);
    }

    ever<GnssConnectionState>(GnssService.to.connectionState, (state) {
      if (state == GnssConnectionState.connected) {
        final type = GnssService.to.activeConnectionType.value;
        final isExternal = type == GnssConnectionType.btSerial ||
            type == GnssConnectionType.ble;

        if (isExternal &&
            autoConnect.value &&
            hasCompleteSettings &&
            !isConnected.value) {
          unawaited(connect().catchError((e) {
            lastError.value = e.tostring();
          }));
        }
      } else if (state == GnssConnectionState.disconnected ||
          state == GnssConnectionState.error) {
        if (isConnected.value) {
          unawaited(disconnect());
        }
      }
    });
  }

  void _ensureReceiverReady() {
    if (!Get.isRegistered<GnssService>() ||
        GnssService.to.connectionState.value != GnssConnectionState.connected) {
      throw NtripException(
          'Nincs csatlakoztatott GNSS vevő - előbb csatlakozz az eszközhöz, '
          'utána indítható az NTRIP korrekció.');
    }
    final type = GnssService.to.activeConnectionType.value;
    if (type == GnssConnectionType.phoneGps) {
      throw NtripException(
          'A telefon beépített GPS-e nem tud RTK korrekciókat fogadni - '
          'az NTRIP használatához külső GNSS vevő szükséges');
    }
  }

  @override
  void onClose() {
    disconnect();
    // hostController.dispose();
    // portController.dispose();
    // mountpointController.dispose();
    // usernameController.dispose();
    // passwordController.dispose();
    super.onClose();
  }

  // ── Kapcsolat ────────────────────────────────────────────────────

  /// Kapcsolódás a casterhez. Megvárja a caster válaszát, és
  /// [NtripException]-t dob értelmes üzenettel, ha valami nem stimmel
  /// (rossz jelszó, rossz mountpoint, időtúllépés, hálózati hiba).
  Future<void> connect({bool bypassReceiverCheck = false}) async {
    if (isConnected.value) return;
    if (!hasCompleteSettings) {
      throw NtripException('Hiányzó NTRIP beállítások (host / mountpoint).');
    }
    if (!bypassReceiverCheck) {
      _ensureReceiverReady();
    }

    lastError.value = '';
    _headerValidated = false;
    _headerBytes.clear();
    receivedBytes.value = 0;
    packetCount.value = 0;
    _connectCompleter = Completer<void>();

    try {
      // FONTOS: String host → a Socket.connect DNS-feloldást is végez,
      // így hostname (pl. www.gnssnet.hu) és IP-cím is működik.
      // (A korábbi InternetAddress(host) csak IP-literált fogadott el.)
      _socket = await Socket.connect(
        host.value.trim(),
        port.value,
        timeout: const Duration(seconds: 5),
      );

      _socket!.add(_toUint8List(_buildNtripHeader()));

      _socketSub = _socket!.listen(
        _onData,
        onError: _onError,
        onDone: _onDone,
      );

      // Megvárjuk, hogy a caster válaszoljon és a fejléc validálódjon.
      await _connectCompleter!.future.timeout(
        const Duration(seconds: 8),
        onTimeout: () =>
            throw NtripException('Időtúllépés — a caster nem válaszolt.'),
      );

      isConnected.value = true;
      connectedSince.value = DateTime.now();
    } on NtripException {
      await _teardown();
      rethrow;
    } on SocketException catch (e) {
      await _teardown();
      throw NtripException('Hálózati hiba: ${e.message}');
    } catch (e) {
      await _teardown();
      throw NtripException('Nem sikerült csatlakozni: $e');
    } finally {
      _connectCompleter = null;
    }
  }

  Future<void> disconnect() async {
    if (!isConnected.value && _socket == null) return;
    await _teardown();
  }

  Future<void> _teardown() async {
    await _socketSub?.cancel();
    _socketSub = null;
    _socket?.destroy();
    _socket = null;
    isConnected.value = false;
    connectedSince.value = null;
    lastRtcmAt.value = null;
    _headerValidated = false;
    _headerBytes.clear();
  }

  void reconnect() async {
    await disconnect();
    await Future.delayed(const Duration(seconds: 1));
    await connect();
  }

  Future<String> testConnection() async {
    if (isConnected.value) {
      throw NtripException('Már van élő NTRIP kapcsolat.');
    }
    try {
      await connect(bypassReceiverCheck: true);
      return 'A caster elfogadta a kapcsolatot - '
          'a beállítások (jelszó, mountpoint) rendben vannak.';
    } finally {
      await disconnect();
    }
  }

  // ── GGA küldés ───────────────────────────────────────────────────

  /// Az NMEA feldolgozó hívja minden GGA mondatnál.
  /// 5 másodpercenként küld egyet az NTRIP casternek.
  void onGgaReceived(String ggaLine, String utcTime) {
    _lastGgaMessage = ggaLine;

    if (!isConnected.value) return;
    if (ggaLine.isEmpty) return;

    final elapsed = DateTime.now().difference(_lastGgaSentTime).inSeconds;
    if (elapsed < 5) return;

    _sendGga(ggaLine);
    ggaSentCount.value++;
    ggaLastSentTime.value = utcTime;
    _lastGgaSentTime = DateTime.now();
  }

  void _sendGga(String ggaMessage) {
    if (_socket == null || !isConnected.value) return;
    _socket!.add(_toUint8List('$ggaMessage\r\n'));
  }

  // ── Belső adatfogadás ────────────────────────────────────────────

  void _onData(Uint8List data) {
    // Amíg a fejléc nincs validálva, a beérkező byte-okat puffereljük
    // és a caster válaszát elemezzük. Így a "ICY 200 OK" / HTTP fejléc
    // vagy egy hibaüzenet SOHA nem kerül RTCM-ként a GNSS vevőbe.
    if (!_headerValidated) {
      _handleHeaderBytes(data);
      return;
    }
    _forwardRtcm(data);
  }

  void _handleHeaderBytes(Uint8List data) {
    _headerBytes.addAll(data);

    // latin1: minden byte dekódolható, 1 byte = 1 karakter,
    // így a szöveg-index megegyezik a byte-indexszel.
    final text = latin1.decode(_headerBytes);

    // Várunk, amíg legalább az első sor megérkezik.
    final firstLineEnd = text.indexOf('\r\n');
    if (firstLineEnd == -1) {
      if (_headerBytes.length > 4096) {
        _failConnect('Érvénytelen caster válasz (nincs fejléc).');
      }
      return;
    }

    final firstLine = text.substring(0, firstLineEnd).trim();

    // Rossz mountpoint → a caster a forrástáblát küldi.
    if (firstLine.contains('SOURCETABLE')) {
      _failConnect(
          'Hibás mountpoint: "${mountpoint.value}" — a caster forrástáblát küldött.');
      return;
    }

    // Hibás hitelesítés.
    if (firstLine.contains('401') || firstLine.contains('403')) {
      _failConnect('Hibás felhasználónév vagy jelszó (${firstLine.trim()}).');
      return;
    }

    final isOk =
        (firstLine.startsWith('ICY') || firstLine.startsWith('HTTP/')) &&
            firstLine.contains('200');

    if (!isOk) {
      _failConnect('Váratlan caster válasz: "$firstLine"');
      return;
    }

    // Siker — meghatározzuk, hol ér véget a fejléc, mert az azt követő
    // byte-ok már RTCM adatok, amiket tovább kell adni a vevőnek.
    int headerEnd;
    if (firstLine.startsWith('ICY')) {
      // NTRIP v1: "ICY 200 OK\r\n" (esetleg + üres sor)
      headerEnd = firstLineEnd + 2;
      if (text.length >= headerEnd + 2 &&
          text.substring(headerEnd, headerEnd + 2) == '\r\n') {
        headerEnd += 2;
      }
    } else {
      // NTRIP v2 / HTTP: teljes fejléc "\r\n\r\n"-ig
      final i = text.indexOf('\r\n\r\n');
      if (i == -1) {
        // A fejléc még nem teljes — várunk a következő csomagra.
        if (_headerBytes.length > 8192) {
          _failConnect('A caster fejléce túl hosszú / hibás.');
        }
        return;
      }
      headerEnd = i + 4;
    }

    _headerValidated = true;

    if (!(_connectCompleter?.isCompleted ?? true)) {
      _connectCompleter!.complete();
    }

    // A fejléc után már megérkezett RTCM byte-ok továbbítása.
    if (_headerBytes.length > headerEnd) {
      final rest = Uint8List.fromList(_headerBytes.sublist(headerEnd));
      _headerBytes.clear();
      _forwardRtcm(rest);
    } else {
      _headerBytes.clear();
    }
  }

  void _forwardRtcm(Uint8List data) {
    receivedBytes.value += data.length; // kumulált (a korábbi = felülírt)
    packetCount.value++;
    lastRtcmAt.value = DateTime.now();
    onRtcmData?.call(data);
  }

  void _failConnect(String message) {
    lastError.value = message;
    if (!(_connectCompleter?.isCompleted ?? true)) {
      _connectCompleter!.completeError(NtripException(message));
    }
    // A teardown-t a connect() catch ága végzi el.
  }

  void _onError(dynamic error) {
    // Kapcsolódás közben: a connect() kapja meg a hibát.
    if (!(_connectCompleter?.isCompleted ?? true)) {
      _connectCompleter!
          .completeError(NtripException('Kapcsolati hiba: $error'));
      return;
    }
    // Élő kapcsolat közben: bontás + értesítés.
    _teardown();
    lastError.value = error.toString();
    Get.snackbar(
      'NTRIP kapcsolat megszakadt',
      error.toString(),
      backgroundColor: const Color(0xFFB71C1C),
      colorText: const Color(0xFFFFFFFF),
    );
  }

  void _onDone() async {
    if (!(_connectCompleter?.isCompleted ?? true)) {
      _connectCompleter!
          .completeError(NtripException('A caster bontotta a kapcsolatot.'));
      return;
    }
    _teardown();
  }

  // ── HTTP fejléc összeállítás ─────────────────────────────────────

  String _buildNtripHeader() {
    final auth = _toBase64('${username.value}:${password.value}');
    final hostHeader = '${host.value}:${port.value}';

    return 'GET /${mountpoint.value} HTTP/1.1\r\n'
        'User-Agent: SharpGps iter.dk\r\n'
        'Accept: */*\r\n'
        'Connection: close\r\n'
        'Authorization: Basic $auth\r\n'
        'Host: $hostHeader\r\n'
        'Ntrip-Version: Ntrip/2.0\r\n'
        '\r\n';
  }

  // ── Beállítások mentése / betöltése ──────────────────────────────
  /// A NtripSettingsController és bármely más UI ezt hívja.
  Future<void> updateSettings({
    required String host,
    required int port,
    required String mountpoint,
    required String username,
    required String password,
    bool? autoConnect,
  }) async {
    this.host.value = host.trim();
    this.port.value = port;
    this.mountpoint.value = mountpoint.trim();
    this.username.value = username.trim();
    this.password.value = password;
    if (autoConnect != null) this.autoConnect.value = autoConnect;

    // _syncControllersFromValues();

    final prefs = await SharedPreferences.getInstance();
    await prefs.setString('ntrip_host', this.host.value);
    await prefs.setInt('ntrip_port', this.port.value);
    await prefs.setString('ntrip_mountpoint', this.mountpoint.value);
    await prefs.setString('ntrip_username', this.username.value);
    await prefs.setBool('ntrip_auto_connect', this.autoConnect.value);

    // A jelszó KIZÁRÓLAG secure storage-ba kerül.
    await _secure.write(key: 'ntrip_password', value: password);
    // Ha korábban plain textben volt a prefs-ben, azt töröljük.
    await prefs.remove('ntrip_password');
  }

  // Future<void> saveSettings() async {
  //   await updateSettings(
  //     host: hostController.text,
  //     port: int.tryParse(portController.text) ?? 2101,
  //     mountpoint: mountpointController.text,
  //     username: usernameController.text,
  //     password: passwordController.text.isNotEmpty
  //         ? passwordController.text
  //         : password.value,
  //   );
  // }

  Future<void> _loadSettings() async {
    final prefs = await SharedPreferences.getInstance();
    host.value = prefs.getString('ntrip_host') ?? '';
    port.value = prefs.getInt('ntrip_port') ?? 2101;
    mountpoint.value = prefs.getString('ntrip_mountpoint') ?? '';
    username.value = prefs.getString('ntrip_username') ?? '';
    password.value = await _secure.read(key: 'ntrip_password') ?? '';
    autoConnect.value = prefs.getBool('ntrip_auto_connect') ?? false;
  }

  // void _syncControllersFromValues() {
  //   hostController.text = host.value;
  //   portController.text = port.value.toString();
  //   mountpointController.text = mountpoint.value;
  //   usernameController.text = username.value;
  //   // Jelszót nem pre-töltjük biztonsági okokból
  // }

  // ── Segédfüggvények ──────────────────────────────────────────────

  String _toBase64(String str) => base64.encode(ascii.encode(str));

  Uint8List _toUint8List(String str) => Uint8List.fromList(str.codeUnits);
}
