import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:meshcore_sar_app/utils/voice_message_parser.dart';

void main() {
  group('VoiceEnvelope', () {
    test('encodes and parses valid envelope', () {
      final env = VoiceEnvelope(
        sessionId: '0000000a',
        mode: VoicePacketMode.mode1200,
        total: 4,
        durationMs: 3000,
        senderKey6: 'aabbccddeeff',
        timestampSec: 1700000000,
      );

      final text = env.encodeText();
      expect(VoiceEnvelope.isVoiceEnvelopeText(text), isTrue);
      expect(text.startsWith('VE2:'), isTrue);
      expect(text.split(':')[1], equals('a'));

      final parsed = VoiceEnvelope.tryParseText(text);
      expect(parsed, isNotNull);
      expect(parsed!.sessionId, equals('0000000a'));
      expect(parsed.mode, equals(VoicePacketMode.mode1200));
      expect(parsed.total, equals(4));
      expect(parsed.durationMs, equals(3000));
      expect(parsed.senderKey6, equals('aabbccddeeff'));
      expect(parsed.version, equals(2));
    });

    test('rejects invalid envelope payload', () {
      final text = 'VE2:bad_sid:1:2:1000:aabbccddeeff:s44we8';
      expect(VoiceEnvelope.tryParseText(text), isNull);
    });

    test('rejects legacy v1 envelope prefix', () {
      const legacy = 'VE1:deadbeef:1:4:3200:aabbccddeeff:1700000000:1';
      expect(VoiceEnvelope.tryParseText(legacy), isNull);
    });
  });

  group('VoiceFetchRequest', () {
    test('encodes and parses valid request', () {
      final req = VoiceFetchRequest(
        sessionId: '0000000a',
        requesterKey6: 'ffeeddccbbaa',
        timestampSec: 1700000001,
      );
      final text = req.encodeText();
      expect(VoiceFetchRequest.isVoiceFetchRequestText(text), isTrue);
      expect(text.startsWith('VR2:'), isTrue);
      expect(text.split(':')[1], equals('a'));

      final parsed = VoiceFetchRequest.tryParseText(text);
      expect(parsed, isNotNull);
      expect(parsed!.sessionId, equals('0000000a'));
      expect(parsed.want, equals('all'));
      expect(parsed.requesterKey6, equals('ffeeddccbbaa'));
      expect(parsed.version, equals(2));
    });

    test('rejects invalid request payload', () {
      expect(
        VoiceFetchRequest.tryParseText(
          'VR2:a:chunk:ffeeddccbbaa:s44we9',
        ),
        isNull,
      );
    });

    test('rejects legacy v1 request prefix', () {
      const legacy = 'VR1:00112233:a:ffeeddccbbaa:1700000001:1';
      expect(VoiceFetchRequest.tryParseText(legacy), isNull);
    });

    test('encodes and parses missing-packet request', () {
      final req = VoiceFetchRequest(
        sessionId: '0000000a',
        want: 'missing',
        missingIndices: const [0, 1, 2, 3, 7],
        requesterKey6: 'ffeeddccbbaa',
        timestampSec: 1700000001,
      );
      final text = req.encodeText();
      expect(text, contains(':m0-3.7:'));

      final parsed = VoiceFetchRequest.tryParseText(text);
      expect(parsed, isNotNull);
      expect(parsed!.want, equals('missing'));
      expect(parsed.missingIndices, equals([0, 1, 2, 3, 7]));
    });

    test('encodes and parses binary fetch request', () {
      final req = VoiceFetchRequest(
        sessionId: '01020304',
        want: 'missing',
        missingIndices: const [1, 4],
        requesterKey6: 'ffeeddccbbaa',
        timestampSec: 1700000001,
      );

      final payload = req.encodeBinary();
      expect(VoiceFetchRequest.isVoiceFetchRequestBinary(payload), isTrue);

      final parsed = VoiceFetchRequest.tryParseBinary(payload);
      expect(parsed, isNotNull);
      expect(parsed!.sessionId, equals('01020304'));
      expect(parsed.want, equals('missing'));
      expect(parsed.missingIndices, equals([1, 4]));
      expect(parsed.requesterKey6, equals('ffeeddccbbaa'));
      expect(parsed.version, equals(2));
    });
  });

  group('VoiceFragmentAck', () {
    test('encodes and parses binary ack', () {
      final ack = VoiceFragmentAck(sessionId: '01020304', index: 7);
      final payload = ack.encodeBinary();
      expect(VoiceFragmentAck.isVoiceFragmentAckBinary(payload), isTrue);
      final parsed = VoiceFragmentAck.tryParseBinary(payload);
      expect(parsed, isNotNull);
      expect(parsed!.sessionId, equals('01020304'));
      expect(parsed.index, equals(7));
    });
  });

  group('VoicePacket backward compatibility', () {
    test('parses legacy V: text format', () {
      final pkt = VoicePacket(
        sessionId: 'a1b2c3d4',
        mode: VoicePacketMode.mode700c,
        index: 0,
        total: 1,
        codec2Data: Uint8List.fromList([1, 2, 3, 4]),
      );
      final encoded = pkt.encodeText();
      final parsed = VoicePacket.tryParseText(encoded);
      expect(parsed, isNotNull);
      expect(parsed!.sessionId, equals('a1b2c3d4'));
      expect(parsed.total, equals(1));
      expect(parsed.codec2Data, equals(Uint8List.fromList([1, 2, 3, 4])));
    });

    test('constructs binary datagram from actual packet data', () {
      final actualCodec2 = Uint8List.fromList([
        0xD3,
        0x19,
        0x7A,
        0x00,
        0xFE,
        0x44,
        0xC1,
        0x2B,
        0x88,
      ]);

      final pkt = VoicePacket(
        sessionId: '01020304',
        mode: VoicePacketMode.mode1300,
        index: 2,
        total: 5,
        codec2Data: actualCodec2,
      );

      final datagram = pkt.encodeBinary();
      expect(datagram[0], equals(0x56)); // magic 'V'
      expect(datagram.sublist(1, 5), equals(Uint8List.fromList([1, 2, 3, 4])));
      expect(datagram[5], equals(VoicePacketMode.mode1300.id));
      expect(datagram[6], equals(2));
      expect(datagram[7], equals(5));
      expect(datagram.sublist(8), equals(actualCodec2));

      final parsed = VoicePacket.tryParseBinary(datagram);
      expect(parsed, isNotNull);
      expect(parsed!.sessionId, equals('01020304'));
      expect(parsed.mode, equals(VoicePacketMode.mode1300));
      expect(parsed.index, equals(2));
      expect(parsed.total, equals(5));
      expect(parsed.codec2Data, equals(actualCodec2));
    });
  });

  group('VoiceWaveform', () {
    test('builds bars from packet bytes', () {
      final packet = VoicePacket(
        sessionId: '1234abcd',
        mode: VoicePacketMode.mode1200,
        index: 0,
        total: 1,
        codec2Data: Uint8List.fromList([
          0,
          255,
          10,
          245,
          120,
          130,
          64,
          192,
          32,
          224,
        ]),
      );

      final bars = VoiceWaveform.buildBarsFromPackets([packet], bars: 8);
      expect(bars.length, equals(8));
      expect(bars.every((v) => v >= 0.0 && v <= 1.0), isTrue);
      expect(bars.any((v) => v > 0.2), isTrue);
    });

    test('returns zeros for missing packet data', () {
      final bars = VoiceWaveform.buildBarsFromPackets(const [], bars: 6);
      expect(bars, equals(List<double>.filled(6, 0.0)));
    });
  });
}
