#include "lora_transport.h"
#include "lora_frame_logic.h"
#include <LoRa.h>
#include <SPI.h>
#include <math.h>

static RxRejectReason g_last_rx_reject_reason = RxRejectReason::None;
static uint32_t g_last_rx_reject_log_ms = 0;
static bool g_last_rx_signal_valid = false;
static int16_t g_last_rx_rssi_dbm = 0;
static float g_last_rx_snr_db = 0.0f;

static void note_reject(RxRejectReason reason) {
  g_last_rx_reject_reason = reason;
  if (SERIAL_DEBUG_MODE) {
    uint32_t now_ms = millis();
    if (now_ms - g_last_rx_reject_log_ms >= 1000) {
      g_last_rx_reject_log_ms = now_ms;
      Serial.printf("lora_rx: reject reason=%s\n", rx_reject_reason_text(reason));
    }
  }
}

RxRejectReason lora_get_last_rx_reject_reason() {
  RxRejectReason reason = g_last_rx_reject_reason;
  g_last_rx_reject_reason = RxRejectReason::None;
  return reason;
}

bool lora_get_last_rx_signal(int16_t &rssi_dbm, float &snr_db) {
  if (!g_last_rx_signal_valid) {
    return false;
  }
  rssi_dbm = g_last_rx_rssi_dbm;
  snr_db = g_last_rx_snr_db;
  return true;
}

void lora_init() {
  SPI.begin(PIN_LORA_SCK, PIN_LORA_MISO, PIN_LORA_MOSI, PIN_LORA_NSS);
  LoRa.setPins(PIN_LORA_NSS, PIN_LORA_RST, PIN_LORA_DIO0);
  LoRa.begin(LORA_FREQUENCY);
  LoRa.setSpreadingFactor(LORA_SPREADING_FACTOR);
  LoRa.setSignalBandwidth(LORA_BANDWIDTH);
  LoRa.setCodingRate4(LORA_CODING_RATE);
  LoRa.enableCrc();
  LoRa.setSyncWord(LORA_SYNC_WORD);
}

bool lora_send(const LoraPacket &pkt) {
  if (LORA_SEND_BYPASS) {
    return true;
  }
  uint32_t t0 = 0;
  if (SERIAL_DEBUG_MODE) {
    t0 = millis();
  }
  LoRa.idle();
  if (pkt.payload_len > LORA_MAX_PAYLOAD) {
    return false;
  }

  uint8_t buffer[1 + 2 + LORA_MAX_PAYLOAD + 2];
  size_t frame_len = 0;
  if (!lora_build_frame(static_cast<uint8_t>(pkt.msg_kind), pkt.device_id_short, pkt.payload, pkt.payload_len,
                        buffer, sizeof(buffer), frame_len)) {
    return false;
  }

  LoRa.beginPacket();
  LoRa.write(buffer, frame_len);
  int result = LoRa.endPacket(false);
  bool ok = result == 1;
  if (SERIAL_DEBUG_MODE) {
    uint32_t tx_ms = millis() - t0;
    if (!ok || tx_ms > 2000) {
      Serial.printf("lora_tx: len=%u total=%lums ok=%u\n",
                    static_cast<unsigned>(frame_len),
                    static_cast<unsigned long>(tx_ms),
                    ok ? 1U : 0U);
    }
  }
  return ok;
}

bool lora_receive(LoraPacket &pkt, uint32_t timeout_ms) {
  uint32_t start = millis();
  while (true) {
    int packet_size = LoRa.parsePacket();
    if (packet_size > 0) {
      g_last_rx_rssi_dbm = static_cast<int16_t>(LoRa.packetRssi());
      g_last_rx_snr_db = LoRa.packetSnr();
      g_last_rx_signal_valid = true;

      if (packet_size < 5) {
        while (LoRa.available()) {
          LoRa.read();
        }
        note_reject(RxRejectReason::LengthMismatch);
        return false;
      }

      uint8_t buffer[1 + 2 + LORA_MAX_PAYLOAD + 2];
      size_t len = 0;
      while (LoRa.available() && len < sizeof(buffer)) {
        buffer[len++] = LoRa.read();
      }
      if (LoRa.available()) {
        while (LoRa.available()) {
          LoRa.read();
        }
        if (SERIAL_DEBUG_MODE) {
          Serial.println("rx_reject: oversize packet drained");
        }
        note_reject(RxRejectReason::LengthMismatch);
        return false;
      }

      if (len < 5) {
        note_reject(RxRejectReason::LengthMismatch);
        return false;
      }

      uint8_t msg_kind = 0;
      uint16_t device_id_short = 0;
      size_t payload_len = 0;
      LoraFrameDecodeStatus status = lora_parse_frame(
          buffer, len, static_cast<uint8_t>(LoraMsgKind::AckDown), &msg_kind, &device_id_short,
          pkt.payload, sizeof(pkt.payload), &payload_len);

      if (status == LoraFrameDecodeStatus::CrcFail) {
        note_reject(RxRejectReason::CrcFail);
        return false;
      }
      if (status == LoraFrameDecodeStatus::InvalidMsgKind) {
        note_reject(RxRejectReason::InvalidMsgKind);
        return false;
      }
      if (status == LoraFrameDecodeStatus::LengthMismatch) {
        note_reject(RxRejectReason::LengthMismatch);
        return false;
      }

      pkt.msg_kind = static_cast<LoraMsgKind>(msg_kind);
      pkt.device_id_short = device_id_short;
      pkt.payload_len = payload_len;
      if (pkt.payload_len > LORA_MAX_PAYLOAD) {
        note_reject(RxRejectReason::LengthMismatch);
        return false;
      }
      pkt.rssi_dbm = g_last_rx_rssi_dbm;
      pkt.snr_db = g_last_rx_snr_db;
      return true;
    }

    if (timeout_ms == 0 || millis() - start >= timeout_ms) {
      return false;
    }
    delay(5);
  }
}

void lora_idle() {
  LoRa.idle();
}

void lora_sleep() {
  LoRa.sleep();
}

void lora_receive_continuous() {
  LoRa.receive();
}

bool lora_receive_window(LoraPacket &pkt, uint32_t timeout_ms) {
  if (timeout_ms == 0) {
    return false;
  }
  g_last_rx_signal_valid = false;
  g_last_rx_rssi_dbm = 0;
  g_last_rx_snr_db = 0.0f;
  LoRa.receive();
  bool got = lora_receive(pkt, timeout_ms);
  LoRa.sleep();
  return got;
}

uint32_t lora_airtime_ms(size_t packet_len) {
  if (packet_len == 0) {
    return 0;
  }

  const double bw = static_cast<double>(LORA_BANDWIDTH);
  const double sf = static_cast<double>(LORA_SPREADING_FACTOR);
  const double cr = static_cast<double>(LORA_CODING_RATE - 4); // coding rate denominator: 4/(4+cr)
  const double tsym = (1 << LORA_SPREADING_FACTOR) / bw;
  const double t_preamble = (static_cast<double>(LORA_PREAMBLE_LEN) + 4.25) * tsym;

  const bool low_data_rate_opt = (LORA_SPREADING_FACTOR >= 11) && (LORA_BANDWIDTH <= 125000);
  const double de = low_data_rate_opt ? 1.0 : 0.0;
  const double ih = 0.0;
  const double crc = 1.0;

  const double payload_symb_nb = 8.0 + max(
    ceil((8.0 * packet_len - 4.0 * sf + 28.0 + 16.0 * crc - 20.0 * ih) / (4.0 * (sf - 2.0 * de))) * (cr + 4.0),
    0.0);
  const double t_payload = payload_symb_nb * tsym;
  const double t_packet = t_preamble + t_payload;

  return static_cast<uint32_t>(ceil(t_packet * 1000.0));
}
