import 'dart:typed_data';
import '../crypto/aes_cipher.dart';
import '../crypto/key_manager.dart';
import '../fec/reed_solomon.dart';
import '../modem/fsk_demodulator.dart';
import 'packet.dart';
import '../../utils/audio_math.dart';
import '../../utils/constants.dart';

/// Receive pipeline: FSK audio → demodulate → RS FEC → decrypt → packet.
///
/// Feed audio chunks from the microphone via [pushAudio]. Decoded voice
/// super-frames (9-byte LPC payloads) are queued in [voiceQueue].
class Deframer {
  final KeyManager     _keys;
  final Uint8List      _sessionId;
  final FskDemodulator _demod = FskDemodulator();

  // Decoded LPC payloads waiting to be synthesised.
  final List<Uint8List> voiceQueue = [];

  // Stats for UI display.
  int rxPackets  = 0;
  int rxErrors   = 0;
  int rxCorrected = 0;

  Deframer(this._keys, this._sessionId);

  // ── Public API ─────────────────────────────────────────────────────

  /// Push a chunk of Float64 audio samples from the microphone.
  ///
  /// Internally demodulates FSK, assembles wire frames, applies FEC,
  /// decrypts, and enqueues decoded voice payloads into [voiceQueue].
  void pushAudio(Float64List samples) {
    final rawFrames = _demod.pushSamples(samples);
    for (final raw in rawFrames) {
      _processWireFrame(raw);
    }
  }

  /// Push raw Int16 LE PCM bytes (as received from AudioRecord).
  void pushPcmBytes(Uint8List pcmBytes) {
    pushAudio(AudioMath.int16BytesToFloat(pcmBytes));
  }

  void reset() {
    _demod.reset();
    voiceQueue.clear();
    rxPackets   = 0;
    rxErrors    = 0;
    rxCorrected = 0;
  }

  // ── Private pipeline ───────────────────────────────────────────────

  /// Process one wire frame (22 bytes) through FEC → CRC → decrypt → queue.
  void _processWireFrame(Uint8List wire) {
    if (wire.length < 22) {
      rxErrors++;
      return;
    }

    // ── Wire layout: [SYNC:2][SEQ:2][TYPE:1][RS_PAYLOAD:15][CRC:2] ──
    final seq  = (wire[2] << 8) | wire[3];
    final type = wire[4];

    // Extract the 15-byte RS-encoded block.
    final rsBlock = Uint8List.fromList(wire.sublist(5, 20));

    // CRC check over bytes [2..19].
    final expectedCrc = AudioMath.crc16(wire.sublist(2, 20));
    final wireCrc     = (wire[20] << 8) | wire[21];
    if (expectedCrc != wireCrc) {
      rxErrors++;
      return; // frame corrupted beyond CRC recovery
    }

    // ── RS(15,11) FEC decode (corrects up to 2 nibble errors) ────────
    final rsOk = ReedSolomon.decodeBlock(rsBlock);
    if (!rsOk) {
      rxErrors++;
      return;
    }
    // Detect whether RS actually corrected any bytes (compare before/after).
    bool wasCorrected = false;
    for (int i = 0; i < rsBlock.length; i++) {
      if (rsBlock[i] != wire[5 + i]) { wasCorrected = true; break; }
    }
    if (wasCorrected) rxCorrected++;

    // Extract the 11-byte data payload.
    final encrypted = ReedSolomon.extractData(rsBlock);

    // ── AES-256-CTR decrypt ───────────────────────────────────────────
    final nonce     = _keys.buildNonce(_sessionId, seq);
    final plaintext = AesCipher.decrypt(encrypted, _keys.key, nonce);

    // ── Parse inner packet structure ──────────────────────────────────
    // plaintext layout: [lpc(9)][seq_low(1)][type_tag(1)]
    // Validate inner type tag matches outer type.
    final innerType = plaintext[10];
    if (innerType != type && type == C.pkTypeVoice) {
      rxErrors++;
      return;
    }

    rxPackets++;

    // Build a Packet for higher-level handlers.
    final pkt = Packet(
      seq:     seq,
      type:    type,
      payload: plaintext,
      valid:   true,
    );

    if (pkt.isVoice) {
      // First 9 bytes are the LPC super-frame.
      voiceQueue.add(Uint8List.fromList(plaintext.sublist(0, 9)));
    }
    // Control packets (handshake, ping) are ignored here;
    // higher layers can subscribe via [SecureChannel].
  }
}
