import 'dart:convert';

const int mcuReportBytes = 145;
const int mcuReportHeaderBytes = 5;
const int mcuReportTailBytes = 3;

class HexReader {
  HexReader(String hexData) {
    final cleanHex = hexData.replaceAll(RegExp(r'\s+'), '').toUpperCase();
    if (cleanHex.isEmpty ||
        cleanHex.length.isOdd ||
        !RegExp(r'^[0-9A-F]+$').hasMatch(cleanHex)) {
      throw const FormatException('数据必须是长度为偶数的十六进制字符串');
    }

    data = cleanHex;
  }

  late final String data;
  int index = 0;

  int get byteIndex => index ~/ 2;

  int get remainingBytes => (data.length - index) ~/ 2;

  String readHex(int bytes) {
    _ensureAvailable(bytes);
    final length = bytes * 2;
    final value = data.substring(index, index + length);
    index += length;
    return value;
  }

  int readUInt8() {
    return int.parse(readHex(1), radix: 16);
  }

  int readUInt16() {
    return int.parse(readHex(2), radix: 16);
  }

  int readUInt16LE() {
    final low = readUInt8();
    final high = readUInt8();
    return low | (high << 8);
  }

  double readTenths() {
    return readUInt16() / 10;
  }

  double readTenthsLE() {
    return readUInt16LE() / 10;
  }

  int readHighByte() {
    return readUInt16() >> 8;
  }

  String readAscii(int bytes) {
    final hex = readHex(bytes);
    final values = <int>[];

    for (var index = 0; index < hex.length; index += 2) {
      values.add(int.parse(hex.substring(index, index + 2), radix: 16));
    }

    return utf8.decode(values, allowMalformed: true).split('\x00').first.trim();
  }

  void _ensureAvailable(int bytes) {
    if (bytes < 0) {
      throw ArgumentError.value(bytes, 'bytes', '字节数必须是非负整数');
    }

    if (remainingBytes < bytes) {
      throw RangeError('数据不足：需要 $bytes 字节，只剩 $remainingBytes 字节');
    }
  }
}

class McuReport {
  const McuReport({
    required this.head,
    required this.direct,
    required this.serviceNum,
    required this.length,
    required this.mainTemperature,
    required this.versionNumber,
    required this.ambientTemperature,
    required this.upperTemperature,
    required this.humidity,
    required this.oxygenConcentration,
    required this.temperatureSetting,
    required this.lowerTemperature,
    required this.humiditySetting,
    required this.oxygenSetting,
    required this.internalExternalCycleState,
    required this.negativeIonSwitch,
    required this.uvLamp,
    required this.inspectionLamp,
    required this.floodLight,
    required this.o2ChangeSlope,
    required this.rightAtomizerTreatmentTime,
    required this.rightInfraredPhysiotherapyTime,
    required this.tempS1,
    required this.tempH1,
    required this.atomizerTreatmentTime,
    required this.infraredPhysiotherapyTime,
    required this.mainTempCorrect,
    required this.auxiliaryTemperatureCorrect,
    required this.o2CorrectStart,
    required this.o2CorrectDiffer,
    required this.humidityCorrect,
    required this.oxygenCorrect,
    required this.eTempCorrect,
    required this.xyyModel,
    required this.unknown,
    required this.wifible,
    required this.levelLight,
    required this.openO2,
    required this.airConditioner,
    required this.co2Correct,
    required this.co2Measurement,
    required this.co2Setting,
    required this.co2FastClearSwitch,
    required this.openO2SettingMax,
    required this.noOpenO2SettingMax,
    required this.windSet,
    required this.rangeData1,
    required this.rangeData2,
    required this.rangeData3,
    required this.co2Safety,
    required this.co2SettingMin,
    required this.blueLight,
    required this.redLight,
    required this.fcSwitch,
    required this.o2SupplyTime,
    required this.totalTime,
    required this.ratioTemp2,
    required this.ratioTemp1,
    required this.sp,
    required this.dp,
    required this.mean,
    required this.pulseRate,
    required this.xyyStatus,
    required this.xyyError1,
    required this.sn,
    required this.topTempCorrect,
    required this.midTempCorrect,
    required this.o2OpenCorrectStart,
    required this.o2OpenCorrectStep,
    required this.o2CorrectMax,
    required this.o2OpenCorrectMax,
    required this.parsedBytes,
  });

  final String head;
  final String direct;
  final String serviceNum;
  final int length;
  final double mainTemperature;
  final double versionNumber;
  final double ambientTemperature;
  final double upperTemperature;
  final int humidity;
  final int oxygenConcentration;
  final double temperatureSetting;
  final double lowerTemperature;
  final int humiditySetting;
  final int oxygenSetting;
  final int internalExternalCycleState;
  final int negativeIonSwitch;
  final int uvLamp;
  final int inspectionLamp;
  final int floodLight;
  final int o2ChangeSlope;
  final int rightAtomizerTreatmentTime;
  final int rightInfraredPhysiotherapyTime;
  final double tempS1;
  final int tempH1;
  final int atomizerTreatmentTime;
  final int infraredPhysiotherapyTime;
  final int mainTempCorrect;
  final int auxiliaryTemperatureCorrect;
  final int o2CorrectStart;
  final int o2CorrectDiffer;
  final int humidityCorrect;
  final int oxygenCorrect;
  final int eTempCorrect;
  final String xyyModel;
  final String unknown;
  final int wifible;
  final int levelLight;
  final int openO2;
  final int airConditioner;
  final int co2Correct;
  final int co2Measurement;
  final int co2Setting;
  final int co2FastClearSwitch;
  final int openO2SettingMax;
  final int noOpenO2SettingMax;
  final int windSet;
  final String rangeData1;
  final String rangeData2;
  final String rangeData3;
  final int co2Safety;
  final int co2SettingMin;
  final int blueLight;
  final int redLight;
  final int fcSwitch;
  final int o2SupplyTime;
  final int totalTime;
  final int ratioTemp2;
  final int ratioTemp1;
  final int sp;
  final int dp;
  final int mean;
  final int pulseRate;
  final int xyyStatus;
  final int xyyError1;
  final String sn;
  final int topTempCorrect;
  final int midTempCorrect;
  final int o2OpenCorrectStart;
  final int o2OpenCorrectStep;
  final int o2CorrectMax;
  final int o2OpenCorrectMax;
  final int parsedBytes;

  Map<String, dynamic> toMap() {
    return {
      'head': head,
      'direct': direct,
      'serviceNum': serviceNum,
      'length': length,
      'mainTemperature': mainTemperature,
      'versionNumber': versionNumber,
      'ambientTemperature': ambientTemperature,
      'upperTemperature': upperTemperature,
      'humidity': humidity,
      'oxygenConcentration': oxygenConcentration,
      'temperatureSetting': temperatureSetting,
      'lowerTemperature': lowerTemperature,
      'humiditySetting': humiditySetting,
      'oxygenSetting': oxygenSetting,
      'internalExternalCycleState': internalExternalCycleState,
      'negativeIonSwitch': negativeIonSwitch,
      'uvLamp': uvLamp,
      'inspectionLamp': inspectionLamp,
      'floodLight': floodLight,
      'o2ChangeSlope': o2ChangeSlope,
      'rightAtomizerTreatmentTime': rightAtomizerTreatmentTime,
      'rightInfraredPhysiotherapyTime': rightInfraredPhysiotherapyTime,
      'tempS1': tempS1,
      'tempH1': tempH1,
      'atomizerTreatmentTime': atomizerTreatmentTime,
      'infraredPhysiotherapyTime': infraredPhysiotherapyTime,
      'mainTempCorrect': mainTempCorrect,
      'auxiliaryTemperatureCorrect': auxiliaryTemperatureCorrect,
      'o2CorrectStart': o2CorrectStart,
      'o2CorrectDiffer': o2CorrectDiffer,
      'humidityCorrect': humidityCorrect,
      'oxygenCorrect': oxygenCorrect,
      'eTempCorrect': eTempCorrect,
      'xyyModel': xyyModel,
      'unknown': unknown,
      'wifible': wifible,
      'levelLight': levelLight,
      'openO2': openO2,
      'airConditioner': airConditioner,
      'co2Correct': co2Correct,
      'co2Measurement': co2Measurement,
      'co2Setting': co2Setting,
      'co2FastClearSwitch': co2FastClearSwitch,
      'openO2SettingMax': openO2SettingMax,
      'noOpenO2SettingMax': noOpenO2SettingMax,
      'windSet': windSet,
      'rangeData1': rangeData1,
      'rangeData2': rangeData2,
      'rangeData3': rangeData3,
      'co2Safety': co2Safety,
      'co2SettingMin': co2SettingMin,
      'blueLight': blueLight,
      'redLight': redLight,
      'fcSwitch': fcSwitch,
      'o2SupplyTime': o2SupplyTime,
      'totalTime': totalTime,
      'ratioTemp2': ratioTemp2,
      'ratioTemp1': ratioTemp1,
      'sp': sp,
      'dp': dp,
      'mean': mean,
      'pulseRate': pulseRate,
      'xyyStatus': xyyStatus,
      'xyyError1': xyyError1,
      'sn': sn,
      'topTempCorrect': topTempCorrect,
      'midTempCorrect': midTempCorrect,
      'o2OpenCorrectStart': o2OpenCorrectStart,
      'o2OpenCorrectStep': o2OpenCorrectStep,
      'o2CorrectMax': o2CorrectMax,
      'o2OpenCorrectMax': o2OpenCorrectMax,
      'parsedBytes': parsedBytes,
    };
  }

  Map<String, String> toMonitorPayload({DateTime? date}) {
    return {
      'sn': sn,
      'date': '${(date ?? DateTime.now()).millisecondsSinceEpoch}',
      'mainTemp': mainTemperature.toStringAsFixed(1),
      'humidity': '$humidity',
      'oxygenConcentration': '$oxygenConcentration',
      'co2Measurement': '$co2Measurement',
      'mainTempSetting': temperatureSetting.toStringAsFixed(1),
      'humiditySetting': '$humiditySetting',
      'oxygenConcentrationSetting': '$oxygenSetting',
      'co2Setting': '$co2Setting',
      'windSet': '$windSet',
      'cycleState': '$internalExternalCycleState',
      'levelLight': '$levelLight',
      'openO2': '$openO2',
      'floodlight': '$floodLight',
      'inspectionLamp': '$inspectionLamp',
      'blueLight': '$blueLight',
      'redLight': '$redLight',
      'airConditioner': '$airConditioner',
      'wifible': '$wifible',
      'co2FastClearSwitch': '$co2FastClearSwitch',
      'rightAtomizerTreatmentTime': '$rightAtomizerTreatmentTime',
      'rightInfraredPhysiotherapyTime': '$rightInfraredPhysiotherapyTime',
      'totalTime': '$totalTime',
      'o2SupplyTime': '$o2SupplyTime',
    };
  }

  @override
  String toString() => toMap().toString();
}

class McuReportParser {
  static McuReport parseHex(String hexData) {
    final data = HexReader(hexData);
    if (data.remainingBytes < mcuReportHeaderBytes) {
      throw RangeError(
        'MCU 上报至少需要 $mcuReportHeaderBytes 字节头，当前只有 ${data.remainingBytes} 字节',
      );
    }

    final head = data.readHex(2);
    final direct = data.readHex(1);
    final serviceNum = data.readHex(1);
    final length = data.readUInt8();
    final payloadBytes = data.remainingBytes >= length + mcuReportTailBytes
        ? length
        : data.remainingBytes;
    final payload =
        payloadBytes > 0 ? HexReader(data.readHex(payloadBytes)) : null;
    int remainingPayloadBytes() => payload?.remainingBytes ?? 0;
    int u16() => remainingPayloadBytes() >= 2 ? payload!.readUInt16LE() : -1;
    double tenths() =>
        remainingPayloadBytes() >= 2 ? payload!.readTenthsLE() : double.nan;
    int highByte() =>
        remainingPayloadBytes() >= 2 ? payload!.readUInt16LE() >> 8 : -1;
    String hex2() => remainingPayloadBytes() >= 2 ? payload!.readHex(2) : '';
    String ascii(int bytes) =>
        remainingPayloadBytes() >= bytes ? payload!.readAscii(bytes) : '';
    String snAscii(int bytes) => _normalizeSn(ascii(bytes));

    return McuReport(
      head: head,
      direct: direct,
      serviceNum: serviceNum,
      length: length,
      mainTemperature: tenths(),
      versionNumber: tenths(),
      ambientTemperature: tenths(),
      upperTemperature: tenths(),
      humidity: u16(),
      oxygenConcentration: u16(),
      temperatureSetting: tenths(),
      lowerTemperature: tenths(),
      humiditySetting: u16(),
      oxygenSetting: u16(),
      internalExternalCycleState: u16(),
      negativeIonSwitch: u16(),
      uvLamp: u16(),
      inspectionLamp: u16(),
      floodLight: u16(),
      o2ChangeSlope: u16(),
      rightAtomizerTreatmentTime: u16(),
      rightInfraredPhysiotherapyTime: u16(),
      tempS1: highByte() / 10,
      tempH1: highByte(),
      atomizerTreatmentTime: u16(),
      infraredPhysiotherapyTime: highByte(),
      mainTempCorrect: highByte(),
      auxiliaryTemperatureCorrect: highByte(),
      o2CorrectStart: highByte(),
      o2CorrectDiffer: highByte(),
      humidityCorrect: highByte(),
      oxygenCorrect: highByte(),
      eTempCorrect: highByte(),
      xyyModel: hex2(),
      unknown: hex2(),
      wifible: highByte(),
      levelLight: highByte(),
      openO2: highByte(),
      airConditioner: highByte(),
      co2Correct: highByte(),
      co2Measurement: u16(),
      co2Setting: u16(),
      co2FastClearSwitch: highByte(),
      openO2SettingMax: highByte(),
      noOpenO2SettingMax: highByte(),
      windSet: highByte(),
      rangeData1: hex2(),
      rangeData2: hex2(),
      rangeData3: hex2(),
      co2Safety: u16(),
      co2SettingMin: u16(),
      blueLight: highByte(),
      redLight: highByte(),
      fcSwitch: highByte(),
      o2SupplyTime: u16(),
      totalTime: u16(),
      ratioTemp2: remainingPayloadBytes() >= 1 ? payload!.readUInt8() : -1,
      ratioTemp1: remainingPayloadBytes() >= 1 ? payload!.readUInt8() : -1,
      sp: highByte(),
      dp: highByte(),
      mean: highByte(),
      pulseRate: highByte(),
      xyyStatus: highByte(),
      xyyError1: highByte(),
      sn: snAscii(10),
      topTempCorrect: highByte(),
      midTempCorrect: highByte(),
      o2OpenCorrectStart: highByte(),
      o2OpenCorrectStep: highByte(),
      o2CorrectMax: highByte(),
      o2OpenCorrectMax: highByte(),
      parsedBytes: mcuReportHeaderBytes + (payload?.byteIndex ?? 0),
    );
  }

  static McuReport parseBytes(List<int> bytes) {
    return parseHex(_bytesToHex(bytes));
  }

  static String _normalizeSn(String value) {
    if (value.isEmpty) return value;

    final buffer = StringBuffer();
    for (var index = 0; index < value.length; index += 2) {
      if (index + 1 < value.length) {
        buffer
          ..write(value[index + 1])
          ..write(value[index]);
      } else {
        buffer.write(value[index]);
      }
    }
    return buffer.toString();
  }

  static String _bytesToHex(List<int> bytes) {
    return bytes
        .map((item) => item.toRadixString(16).padLeft(2, '0'))
        .join()
        .toUpperCase();
  }
}

class McuReportFrameDecoder {
  final List<int> _buffer = [];

  int get bufferedBytes => _buffer.length;

  String get bufferedHexPreview => _buffer
      .take(24)
      .map((item) => item.toRadixString(16).padLeft(2, '0'))
      .join()
      .toUpperCase();

  List<McuReport> addBytes(List<int> bytes) {
    if (bytes.isEmpty) {
      return const [];
    }

    _buffer.addAll(bytes);
    final reports = <McuReport>[];

    while (_buffer.length >= mcuReportHeaderBytes) {
      final headIndex = _findReportHeadIndex();
      if (headIndex < 0) {
        if (_buffer.length > 3) {
          _buffer.removeRange(0, _buffer.length - 3);
        }
        break;
      }

      if (headIndex > 0) {
        _buffer.removeRange(0, headIndex);
      }

      if (_buffer.length < mcuReportHeaderBytes) {
        break;
      }

      final payloadLength = _buffer[4];
      final frameBytes =
          mcuReportHeaderBytes + payloadLength + mcuReportTailBytes;
      if (_buffer.length < frameBytes) {
        reports.add(McuReportParser.parseBytes(List<int>.from(_buffer)));
        break;
      }

      final frame = List<int>.from(_buffer.take(frameBytes));
      if (frame.last != 0x16) {
        _buffer.removeAt(0);
        continue;
      }

      reports.add(McuReportParser.parseBytes(frame));
      _buffer.removeRange(0, frameBytes);
    }

    return reports;
  }

  int _findReportHeadIndex() {
    for (var index = 0; index <= _buffer.length - 4; index++) {
      if (_buffer[index] == 0x68 &&
          _buffer[index + 1] == 0x02 &&
          _buffer[index + 2] == 0x01 &&
          _buffer[index + 3] == 0x01) {
        return index;
      }
    }
    return -1;
  }

  void clear() {
    _buffer.clear();
  }
}
