import 'dart:typed_data';

import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_sar_app/models/contact.dart';
import 'package:meshcore_sar_app/providers/contacts_provider.dart';
import 'package:meshcore_sar_app/services/cayenne_lpp_parser.dart';
import 'package:shared_preferences/shared_preferences.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  Uint8List createPublicKey(int seed) {
    return Uint8List.fromList(List<int>.generate(32, (index) => seed + index));
  }

  Contact createContact({
    required Uint8List key,
    required ContactType type,
    String? name,
  }) {
    return Contact(
      publicKey: key,
      type: type,
      flags: 0,
      outPathLen: 0,
      outPath: Uint8List(64),
      advName: name ?? 'Test Contact',
      lastAdvert: DateTime.now().millisecondsSinceEpoch ~/ 1000,
      advLat: (46.0569 * 1e6).toInt(),
      advLon: (14.5058 * 1e6).toInt(),
      lastMod: DateTime.now().millisecondsSinceEpoch ~/ 1000,
    );
  }

  group('ContactsProvider.updateTelemetry', () {
    late ContactsProvider provider;
    late Uint8List publicKey;

    setUp(() {
      SharedPreferences.setMockInitialValues({});
      provider = ContactsProvider();
      publicKey = Uint8List.fromList([
        0xAA,
        0xBB,
        0xCC,
        0xDD,
        0xEE,
        0xFF,
        0x01,
        0x02,
        0x03,
        0x04,
        0x05,
        0x06,
        0x07,
        0x08,
        0x09,
        0x0A,
        0x0B,
        0x0C,
        0x0D,
        0x0E,
        0x0F,
        0x10,
        0x11,
        0x12,
        0x13,
        0x14,
        0x15,
        0x16,
        0x17,
        0x18,
        0x19,
        0x1A,
      ]);

      provider.addOrUpdateContact(
        Contact(
          publicKey: publicKey,
          type: ContactType.chat,
          flags: 0,
          outPathLen: 0,
          outPath: Uint8List(64),
          advName: 'Test Contact',
          lastAdvert: DateTime.now().millisecondsSinceEpoch ~/ 1000,
          advLat: (46.0569 * 1e6).toInt(),
          advLon: (14.5058 * 1e6).toInt(),
          lastMod: DateTime.now().millisecondsSinceEpoch ~/ 1000,
        ),
      );
    });

    test('ignores telemetry GPS at 0,0 and keeps advert location', () {
      final lppData = CayenneLppParser.createGpsData(
        latitude: 0.0,
        longitude: 0.0,
      );

      provider.updateTelemetry(publicKey.sublist(0, 6), lppData);
      final updated = provider.findContactByKey(publicKey)!;

      expect(updated.telemetry, isNotNull);
      expect(updated.telemetry!.gpsLocation, isNull);
      expect(updated.displayLocation, isNotNull);
      expect(updated.displayLocation!.latitude, closeTo(46.0569, 0.000001));
      expect(updated.displayLocation!.longitude, closeTo(14.5058, 0.000001));
    });

    test('keeps valid telemetry GPS and uses it for display location', () {
      final lppData = CayenneLppParser.createGpsData(
        latitude: 45.0001,
        longitude: 13.9999,
      );

      provider.updateTelemetry(publicKey.sublist(0, 6), lppData);
      final updated = provider.findContactByKey(publicKey)!;

      expect(updated.telemetry, isNotNull);
      expect(updated.telemetry!.gpsLocation, isNotNull);
      expect(
        updated.telemetry!.gpsLocation!.latitude,
        closeTo(45.0001, 0.0001),
      );
      expect(
        updated.telemetry!.gpsLocation!.longitude,
        closeTo(13.9999, 0.0001),
      );
      expect(updated.displayLocation, isNotNull);
      expect(updated.displayLocation!.latitude, closeTo(45.0001, 0.0001));
      expect(updated.displayLocation!.longitude, closeTo(13.9999, 0.0001));
    });

    test(
      'retains last valid gps for chat/repeater/room when telemetry gps is invalid or missing',
      () {
        final contactTypes = <ContactType>[
          ContactType.chat,
          ContactType.repeater,
          ContactType.room,
        ];

        for (var i = 0; i < contactTypes.length; i++) {
          final scopedProvider = ContactsProvider();
          final scopedKey = createPublicKey(16 + i);
          final contactType = contactTypes[i];
          scopedProvider.addOrUpdateContact(
            createContact(
              key: scopedKey,
              type: contactType,
              name: 'Contact ${contactType.name}',
            ),
          );

          final validGps = CayenneLppParser.createGpsData(
            latitude: 45.1234,
            longitude: 13.8765,
          );
          scopedProvider.updateTelemetry(scopedKey.sublist(0, 6), validGps);

          // No GPS frame should keep previous valid GPS.
          final batteryOnly = CayenneLppParser.createBatteryData(3.8);
          scopedProvider.updateTelemetry(scopedKey.sublist(0, 6), batteryOnly);

          var updated = scopedProvider.findContactByKey(scopedKey)!;
          expect(updated.telemetry, isNotNull);
          expect(updated.telemetry!.gpsLocation, isNotNull);
          expect(
            updated.telemetry!.gpsLocation!.latitude,
            closeTo(45.1234, 0.0001),
          );
          expect(
            updated.telemetry!.gpsLocation!.longitude,
            closeTo(13.8765, 0.0001),
          );

          // Invalid 0,0 GPS frame should also keep previous valid GPS.
          final invalidGps = CayenneLppParser.createGpsData(
            latitude: 0.0,
            longitude: 0.0,
          );
          scopedProvider.updateTelemetry(scopedKey.sublist(0, 6), invalidGps);

          updated = scopedProvider.findContactByKey(scopedKey)!;
          expect(updated.telemetry, isNotNull);
          expect(updated.telemetry!.gpsLocation, isNotNull);
          expect(
            updated.telemetry!.gpsLocation!.latitude,
            closeTo(45.1234, 0.0001),
          );
          expect(
            updated.telemetry!.gpsLocation!.longitude,
            closeTo(13.8765, 0.0001),
          );
        }
      },
    );
  });
}
