import 'dart:convert';
import 'dart:typed_data';

import 'package:flutter_test/flutter_test.dart';
import 'package:qlyra/core/webpush/max_web_protocol.dart';

Uint8List _hex(String value) {
  final bytes = Uint8List(value.length ~/ 2);
  for (var i = 0; i < bytes.length; i++) {
    bytes[i] = int.parse(value.substring(i * 2, i * 2 + 2), radix: 16);
  }
  return bytes;
}

const _compressedResponseHex =
    '0a010000000601000018f00782a6726567696f6ea25a5aa973796e746865746963c3';

void main() {
  test('заголовок кадра совпадает с синтетическим вектором', () {
    final frame = _hex(_compressedResponseHex);
    expect(frame[0], MaxWebFraming.protocolVersion);
    final decoded = MaxWebFraming.decode(frame);
    expect(decoded.cmd, MaxWebCmd.ok);
    expect(decoded.opcode, 6);
    expect(decoded.isOk, isTrue);
  });

  test('распаковка LZ4 и msgpack из синтетического кадра', () {
    final decoded = MaxWebFraming.decode(_hex(_compressedResponseHex));
    final payload = decoded.payload as Map<Object?, Object?>;
    expect(payload['region'], 'ZZ');
    expect(payload['synthetic'], isTrue);
    expect(payload.length, 2);
  });

  test('кодирование кадра сохраняет контракт заголовка', () {
    final actual = MaxWebFraming.encode(
      cmd: 0,
      seq: 2,
      opcode: 6,
      payload: <String, Object?>{'synthetic': true},
    );
    expect(
      actual.sublist(0, MaxWebFraming.headerSize),
      _hex('0a00000200060000000c'),
    );
  });

  test('msgpack переживает круговой рейс', () {
    final source = <String, Object?>{
      'enabled': true,
      'count': 40,
      'offset': -1,
      'nested': <String, Object?>{
        'a': null,
        'b': 3.5,
        'c': <Object?>[1, 'два', false],
      },
    };
    final restored =
        MaxMsgpack.decode(MaxMsgpack.encode(source)) as Map<Object?, Object?>;
    expect(restored['enabled'], true);
    expect(restored['count'], 40);
    expect(restored['offset'], -1);
    final nested = restored['nested'] as Map<Object?, Object?>;
    expect(nested['a'], isNull);
    expect(nested['b'], 3.5);
    expect((nested['c'] as List<Object?>)[1], 'два');
  });

  test('ext(1) разворачивается во вложенное число', () {
    final pollingInterval =
        MaxMsgpack.decode(
              Uint8List.fromList([
                0x81,
                0xA8,
                0x69,
                0x6E,
                0x74,
                0x65,
                0x72,
                0x76,
                0x61,
                0x6C,
                0xC7,
                0x03,
                0x01,
                0xD1,
                0x13,
                0x88,
              ]),
            )
            as Map<Object?, Object?>;
    expect(pollingInterval['interval'], 5000);

    final expiresAt = MaxMsgpack.decode(
      Uint8List.fromList([
        0xC7,
        0x09,
        0x01,
        0xD3,
        0x00,
        0x00,
        0x01,
        0xA0,
        0x25,
        0x5B,
        0xE9,
        0xD2,
      ]),
    );
    expect(expiresAt, 1787333175762);
  });

  test('LZ4 разворачивает перекрывающиеся совпадения', () {
    final compressed = Uint8List.fromList([
      0x6E,
      0x71,
      0x6C,
      0x79,
      0x72,
      0x61,
      0x20,
      0x06,
      0x00,
      0x46,
      0x70,
      0x75,
      0x73,
      0x68,
      0x05,
      0x00,
      0x50,
      0x20,
      0x70,
      0x75,
      0x73,
      0x68,
    ]);
    final result = Lz4Block.decompress(compressed, 256);
    expect(utf8.decode(result), 'qlyra qlyra qlyra qlyra push push push push');
  });

  test('отклоняет кадр с несовпадающей длиной', () {
    expect(
      () => MaxWebFraming.decode(_hex('0a000002000600000003aa')),
      throwsFormatException,
    );
  });

  test('ограничивает распаковку LZ4', () {
    expect(
      () => Lz4Block.decompress(
        Uint8List.fromList([0x1f, 0x41, 0x01, 0x00, 0x00]),
        8,
      ),
      throwsFormatException,
    );
  });
}
