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

import 'package:flutter_test/flutter_test.dart';
import 'package:kuaishai2/features/device/models/device_state.dart';
import 'package:kuaishai2/features/device/services/device_log.dart';
import 'package:kuaishai2/features/device/services/device_message.dart';
import 'package:kuaishai2/features/device/services/json_protocol.dart';
import 'package:kuaishai2/features/device/services/task_payload.dart';
import 'package:kuaishai2/features/programs/models/program.dart';
import 'package:kuaishai2/features/programs/models/step.dart';

void main() {
  group('DeviceMessageType', () {
    test('wire names match protocol', () {
      expect(DeviceMessageType.deviceInfo.wireName, 'device_info');
      expect(DeviceMessageType.createTask.wireName, 'create_task');
      expect(DeviceMessageType.lightControl.wireName, 'light_control');
      expect(DeviceMessageType.control.wireName, 'control');
    });

    test('fromWire round-trip', () {
      for (final t in DeviceMessageType.values) {
        expect(DeviceMessageType.fromWire(t.wireName), t);
      }
      expect(DeviceMessageType.fromWire(null), isNull);
      expect(DeviceMessageType.fromWire('nope'), isNull);
    });
  });

  group('DeviceMessage', () {
    test('toJson 与数据模型一致', () {
      final msg = DeviceMessage(
        messageId: 'abc-1',
        type: DeviceMessageType.deviceInfo,
        data: const {
          'door_status': 'open',
          'task_status': 'running',
          'light_status': 'on',
        },
      );
      final json = msg.toJson();
      expect(json['message_id'], 'abc-1');
      expect(json['type'], 'device_info');
      expect(json['ack'], isNull);
      expect(json['need_ack'], isFalse);
      expect((json['data'] as Map)['door_status'], 'open');
    });

    test('encode/decode 闭环', () {
      final src = DeviceMessage(
        messageId: 'm-001',
        type: DeviceMessageType.lightControl,
        data: const {'status': 'on'},
        ack: 'reply-001',
        needAck: true,
      );
      final raw = src.encode();
      final parsed = DeviceMessage.decode(raw);
      expect(parsed, isNotNull);
      expect(parsed!.messageId, 'm-001');
      expect(parsed.type, DeviceMessageType.lightControl);
      expect(parsed.ack, 'reply-001');
      expect(parsed.needAck, isTrue);
      expect(parsed.data['status'], 'on');
    });

    test('decode 非法 JSON 返回 null', () {
      expect(DeviceMessage.decode('not-json'), isNull);
      expect(DeviceMessage.decode('{"foo":1}'), isNull);
      expect(DeviceMessage.decode(jsonEncode({
        'message_id': '',
        'type': 'device_info',
        'data': {},
      })), isNull);
    });

    test('ackFor 工厂生成应答消息', () {
      final original = DeviceMessage(
        messageId: 'orig-1',
        type: DeviceMessageType.createTask,
        data: const {'k': 'v'},
      );
      final ack = DeviceMessage.ackFor(original, {'result': 'ok'});
      expect(ack.ack, 'orig-1');
      expect(ack.type, DeviceMessageType.createTask);
      expect(ack.data['result'], 'ok');
    });
  });

  group('MessageIdGenerator', () {
    test('连续生成的 ID 互不相同', () {
      final gen = MessageIdGenerator();
      final ids = List.generate(100, (_) => gen.next()).toSet();
      expect(ids.length, 100);
    });
  });

  group('JsonProtocolService', () {
    test('encode + tryDecode 闭环', () {
      final p = JsonProtocolService();
      final msg = DeviceMessage(
        messageId: 'p-1',
        type: DeviceMessageType.control,
        data: const {'status': 'stop'},
      );
      final bytes = p.encode(msg);
      // 4 字节长度前缀 + 至少 1 字节 JSON + 1 字节换行
      expect(bytes.length, greaterThan(5));

      final (decoded, consumed) = p.tryDecode(bytes);
      expect(consumed, bytes.length);
      expect(decoded, isNotNull);
      expect(decoded!.type, DeviceMessageType.control);
      expect(decoded.data['status'], 'stop');
    });

    test('不足一帧时返回 null', () {
      final p = JsonProtocolService();
      final partial = Uint8List.fromList([0x00, 0x00, 0x00]); // 只有 3 字节长度前缀
      final (decoded, consumed) = p.tryDecode(partial);
      expect(decoded, isNull);
      expect(consumed, 0);
    });

    test('分片到达也能解析', () {
      final p = JsonProtocolService();
      final msg = DeviceMessage(
        messageId: 'frag-1',
        type: DeviceMessageType.createTask,
        data: const {'x': 1},
      );
      final bytes = p.encode(msg);

      // 拆成 1 字节 / 剩余两段
      final first = bytes.sublist(0, 1);
      final second = bytes.sublist(1, 4);
      final third = bytes.sublist(4);

      expect(p.tryDecode(first).$1, isNull);
      final (m1, _) = p.tryDecode(second);
      expect(m1, isNull);
      final (m2, consumed) = p.tryDecode(third);
      expect(m2, isNotNull);
      expect(m2!.messageId, 'frag-1');
      // consumed = 4 字节长度前缀 + json 长度 + 1 字节换行
      expect(consumed, bytes.length);
    });

    test('长度字段异常时丢弃首字节重新对齐', () {
      final p = JsonProtocolService();
      // 长度 = 0xFFFFFF 不可能
      final corrupted = Uint8List.fromList([0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x01, 0x7B, 0x7D, 0x0A]);
      final (decoded, _) = p.tryDecode(corrupted);
      // 长度异常 -> 丢弃 1 字节 + 下一帧有效
      // 第二帧 length=1, json='{', 缺少换行也允许
      expect(decoded, isNull);
    });
  });

  group('TaskPayload → DeviceMessage', () {
    test('构造 create_task 消息字段与文档一致', () {
      final program = Program(
        id: 1,
        code: 'P001',
        name: 'Demo',
        createdAt: '2026-06-04',
        temperature: 50,
        airflowTime: 60,
      );
      final steps = [
        Step(
          id: 1,
          programId: 1,
          stepNo: 1,
          position: 'A1',
          name: '混合',
          mixTime: 60,
          magnetTime: 30,
          volume: 100,
          speed: 5,
        ),
      ];
      final payload = TaskPayload.fromProgram(program, steps);
      final msg = payload.toMessage('mid-1', needAck: true);

      expect(msg.type, DeviceMessageType.createTask);
      expect(msg.needAck, isTrue);
      expect(msg.data['temperature'], 50);
      expect(msg.data['airflowtime'], 60);

      final stepJson = (msg.data['steps'] as List).first as Map<String, dynamic>;
      expect(stepJson['no'], 1);
      expect(stepJson['slot'], 1); // A1 -> 1
      expect(stepJson['mixtime'], 60);
      expect(stepJson['pulltime'], 30);
      expect(stepJson['volume'], 100);
      expect(stepJson['speed'], 5);
    });
  });

  group('DeviceState model', () {
    test('copyWith 不会修改未传字段', () {
      const s = DeviceState(
        lightStatus: DeviceLightStatus.on,
        doorStatus: DeviceDoorStatus.open,
        taskStatus: DeviceTaskStatus.running,
        lastInfoAt: null,
      );
      final s2 = s.copyWith(lightStatus: DeviceLightStatus.off);
      expect(s2.lightStatus, DeviceLightStatus.off);
      expect(s2.doorStatus, DeviceDoorStatus.open);
      expect(s2.taskStatus, DeviceTaskStatus.running);
    });

    test('infoStale 在 10 秒后为 true', () {
      final past = DateTime.now().subtract(const Duration(seconds: 30));
      final s = DeviceState(lastInfoAt: past);
      expect(s.infoStale, isTrue);
    });
  });

  group('DeviceLog', () {
    test('summarizeData 截断长字符串与多余键', () {
      final data = {
        'a': 1,
        'b': 'short',
        'c': 'x' * 200,
        'd': [1, 2, 3],
        'e': true,
        'f': null,
        'g': 'extra',
        'h': 'extra2',
        'i': 'extra3',
      };
      final summary = DeviceLog.summarizeData(data, maxKeys: 4);
      expect(summary.contains('a=1'), isTrue);
      expect(summary.contains('b="short"'), isTrue);
      expect(summary.contains('…'), isTrue); // 长字符串被截断
      expect(summary.contains('…'), isTrue);
      expect(summary.contains(', ...'), isTrue); // 多余键被截断
    });

    test('summarizeData 空 Map / null 友好处理', () {
      expect(DeviceLog.summarizeData(null), '{}');
      expect(DeviceLog.summarizeData({}), '{}');
    });

    test('info / warn / severe 不抛异常', () {
      // 仅验证调用不抛异常；dart:developer.log 在测试环境无可见输出
      DeviceLog.info('hello');
      DeviceLog.warn('warn', error: StateError('x'));
      DeviceLog.severe('severe', error: 'err');
    });
  });
}
