#include "lora_radio.h"
#include <string.h>
#include "esp_check.h"
#include "esp_log.h"
#include "esp_timer.h"
#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"

#include "esp_lora_1121.h"
#include "lr1121_common/lr1121_common.h"
#include "lr11xx_driver/lr11xx_hal.h"
#include "lr11xx_driver/lr11xx_radio.h"
#include "lr11xx_driver/lr11xx_regmem.h"
#include "lr11xx_driver/lr11xx_system.h"

static const char *TAG = "lora_radio";

#define LR1121_SPI_FREQ_HZ       (8 * 1000 * 1000)
#define LR1121_SPI_MODE          (0)
#define LR1121_RX_CONTINUOUS     (0xFFFFFF)
#define LR1121_SYNC_WORD         (0x12)  // Private network
#define LR1121_PA_RAMP           (LR11XX_RADIO_RAMP_48_US)

static lr1121_t s_lr = {0};
static spi_device_handle_t s_spi = NULL;
static bool s_is_tx = false;
static lora_params_t s_params;
static lora_metrics_t s_metrics = { .rssi_dbm = -120, .snr_db = -20, .last_status = 0 };
static char s_last_payload[96] = "(no data)";
static size_t s_last_payload_len = 0;
static int64_t s_last_rx_restart_us = 0;
static bool s_tx_in_progress = false;
static int64_t s_last_tx_us = 0;

static inline esp_err_t lr_status_to_esp(lr11xx_status_t st)
{
    return (st == LR11XX_STATUS_OK) ? ESP_OK : ESP_FAIL;
}

static uint32_t cmhz_to_hz(int cmhz)
{
    if (cmhz <= 0) {
        return 0;
    }
    return (uint32_t)cmhz * 10000U;
}

static lr11xx_radio_lora_sf_t map_sf(int sf)
{
    switch (sf) {
    case 5: return LR11XX_RADIO_LORA_SF5;
    case 6: return LR11XX_RADIO_LORA_SF6;
    case 7: return LR11XX_RADIO_LORA_SF7;
    case 8: return LR11XX_RADIO_LORA_SF8;
    case 9: return LR11XX_RADIO_LORA_SF9;
    case 10: return LR11XX_RADIO_LORA_SF10;
    case 11: return LR11XX_RADIO_LORA_SF11;
    case 12: return LR11XX_RADIO_LORA_SF12;
    default: return LR11XX_RADIO_LORA_SF7;
    }
}

static lr11xx_radio_lora_bw_t map_bw(int bw_khz)
{
    switch (bw_khz) {
    case 125: return LR11XX_RADIO_LORA_BW_125;
    case 250: return LR11XX_RADIO_LORA_BW_250;
    case 500: return LR11XX_RADIO_LORA_BW_500;
    default:  return LR11XX_RADIO_LORA_BW_125;
    }
}

static lr11xx_radio_lora_cr_t map_cr(int cr)
{
    switch (cr) {
    case 5: return LR11XX_RADIO_LORA_CR_4_5;
    case 6: return LR11XX_RADIO_LORA_CR_4_6;
    case 7: return LR11XX_RADIO_LORA_CR_4_7;
    case 8: return LR11XX_RADIO_LORA_CR_4_8;
    default: return LR11XX_RADIO_LORA_CR_4_5;
    }
}

static void sanitize_params(lora_params_t *p)
{
    if (!p) {
        return;
    }
    if (p->freq_centi_mhz < 15000) p->freq_centi_mhz = 15000;   // 150 MHz мінімум
    if (p->freq_centi_mhz > 250000) p->freq_centi_mhz = 250000; // 2.5 GHz максимум
    if (p->bw_khz != 125 && p->bw_khz != 250 && p->bw_khz != 500) {
        p->bw_khz = 125;
    }
    if (p->sf < 5) p->sf = 5;
    if (p->sf > 12) p->sf = 12;
    if (p->cr < 5) p->cr = 5;
    if (p->cr > 8) p->cr = 8;
    int max_dbm = 22;
    // HF port (L/S/2.4 GHz) має нижчу допустиму потужність
    if (p->freq_centi_mhz >= 150000) {
        max_dbm = 13;
    }
    if (p->tx_power_dbm < -17) p->tx_power_dbm = -17;
    if (p->tx_power_dbm > max_dbm) p->tx_power_dbm = max_dbm;
    if (p->preamble_syms == 0) p->preamble_syms = 8;
    if (p->payload_len == 0 && p->header_implicit) {
        p->payload_len = 1;
    }
}

static void lr1121_calibrate_image_for_freq(uint32_t freq_hz)
{
    // Використовуємо універсальне калібрування за частотою в MHz ±10 MHz
    uint16_t mhz = (uint16_t)(freq_hz / 1000000U);
    uint16_t f1 = (mhz > 10) ? (uint16_t)(mhz - 10) : mhz;
    uint16_t f2 = (uint16_t)(mhz + 10);
    lr11xx_system_calibrate_image_in_mhz(&s_lr, f1, f2);
}

static esp_err_t radio_setup_spi(void)
{
    spi_bus_config_t buscfg = {
        .mosi_io_num = CONFIG_LORA_PIN_MOSI,
        .miso_io_num = CONFIG_LORA_PIN_MISO,
        .sclk_io_num = CONFIG_LORA_PIN_SCK,
        .quadwp_io_num = -1,
        .quadhd_io_num = -1,
    };
    esp_err_t err = spi_bus_initialize(CONFIG_LORA_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO);
    if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
        return err;
    }

    if (s_spi) {
        return ESP_OK;
    }

    spi_device_interface_config_t devcfg = {
        .clock_speed_hz = LR1121_SPI_FREQ_HZ,
        .mode = LR1121_SPI_MODE,
        .spics_io_num = -1,      // CS керуємо вручну в HAL
        .queue_size = 2,
    };
    err = spi_bus_add_device(CONFIG_LORA_SPI_HOST, &devcfg, &s_spi);
    if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
        return err;
    }
    return ESP_OK;
}

static esp_err_t lr1121_hw_init(void)
{
    lora_init_io_context(&s_lr,
                         CONFIG_LORA_PIN_CS,
                         CONFIG_LORA_PIN_RST,
                         CONFIG_LORA_PIN_BUSY,
                         CONFIG_LORA_PIN_DIO1);
    lora_init_io(&s_lr);
    lora_spi_init(&s_lr, s_spi);
    return ESP_OK;
}

static esp_err_t lr1121_system_boot(void)
{
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_reset(&s_lr)), TAG, "reset");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_hal_wakeup(&s_lr)), TAG, "wakeup");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_standby(&s_lr, LR11XX_SYSTEM_STANDBY_CFG_XOSC)), TAG, "standby");
    const lr11xx_system_reg_mode_t reg_mode = smtc_shield_lr11xx_common_get_reg_mode();
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_reg_mode(&s_lr, reg_mode)), TAG, "reg mode");

    const lr11xx_system_rfswitch_cfg_t *rf_switch = smtc_shield_lr11xx_common_get_rf_switch_cfg();
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_dio_as_rf_switch(&s_lr, rf_switch)), TAG, "rf switch");

    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_tcxo_mode(&s_lr, LR11XX_SYSTEM_TCXO_CTRL_3_0V, 300)), TAG, "tcxo");

    const smtc_shield_lr11xx_lfclk_cfg_t *lf = smtc_shield_lr11xx_common_get_lfclk_cfg();
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_cfg_lfclk(&s_lr, lf->lf_clk_cfg, lf->wait_32k_ready)), TAG, "lfclk");

    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_clear_errors(&s_lr)), TAG, "clear errors");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_calibrate(&s_lr, 0x3F)), TAG, "calibrate");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_clear_irq_status(&s_lr, LR11XX_SYSTEM_IRQ_ALL_MASK)), TAG, "clear irq");

    lr11xx_system_version_t ver = {0};
    if (lr11xx_system_get_version(&s_lr, &ver) == LR11XX_STATUS_OK) {
        ESP_LOGI(TAG, "LR1121 ver hw=0x%02X type=0x%02X fw=0x%04X",
                 ver.hw, ver.type, ver.fw);
    }
    return ESP_OK;
}

static esp_err_t lr1121_apply_lora(const lora_params_t *p)
{
    uint32_t freq_hz = cmhz_to_hz(p->freq_centi_mhz);
    lr11xx_radio_lora_bw_t bw = map_bw(p->bw_khz);
    lr11xx_radio_lora_sf_t sf = map_sf(p->sf);
    lr11xx_radio_lora_cr_t cr = map_cr(p->cr);

    lr11xx_radio_mod_params_lora_t mod = {
        .sf = sf,
        .bw = bw,
        .cr = cr,
        .ldro = smtc_shield_lr11xx_common_compute_lora_ldro(sf, bw),
    };
    lr11xx_radio_pkt_params_lora_t pkt = {
        .preamble_len_in_symb = p->preamble_syms,
        .header_type = p->header_implicit ? LR11XX_RADIO_LORA_PKT_IMPLICIT : LR11XX_RADIO_LORA_PKT_EXPLICIT,
        .pld_len_in_bytes = p->payload_len,
        .crc = p->crc_on ? LR11XX_RADIO_LORA_CRC_ON : LR11XX_RADIO_LORA_CRC_OFF,
        .iq = p->iq_invert ? LR11XX_RADIO_LORA_IQ_INVERTED : LR11XX_RADIO_LORA_IQ_STANDARD,
    };

    const lr11xx_radio_rssi_calibration_table_t *rssi_cal = smtc_shield_lr11xx_get_rssi_calibration_table(freq_hz);
    const smtc_shield_lr11xx_pa_pwr_cfg_t *pa_cfg = smtc_shield_lr1121mb1gis_get_pa_pwr_cfg(freq_hz, p->tx_power_dbm);
    if (pa_cfg == NULL || rssi_cal == NULL) {
        return ESP_ERR_INVALID_ARG;
    }

    lr1121_calibrate_image_for_freq(freq_hz);
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_pkt_type(&s_lr, LR11XX_RADIO_PKT_TYPE_LORA)), TAG, "pkt type");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_rf_freq(&s_lr, freq_hz)), TAG, "freq");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_rssi_calibration(&s_lr, rssi_cal)), TAG, "rssi cal");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_pa_cfg(&s_lr, &pa_cfg->pa_config)), TAG, "pa cfg");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_tx_params(&s_lr, pa_cfg->power, LR1121_PA_RAMP)), TAG, "tx params");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_rx_tx_fallback_mode(&s_lr, LR11XX_RADIO_FALLBACK_STDBY_RC)), TAG, "fallback");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_cfg_rx_boosted(&s_lr, true)), TAG, "rx boost");

    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_mod_params(&s_lr, &mod)), TAG, "mod params");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_pkt_params(&s_lr, &pkt)), TAG, "pkt params");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_sync_word(&s_lr, LR1121_SYNC_WORD)), TAG, "sync word");

    lr11xx_system_irq_mask_t irq_mask = LR11XX_SYSTEM_IRQ_RX_DONE | LR11XX_SYSTEM_IRQ_TIMEOUT | LR11XX_SYSTEM_IRQ_HEADER_ERROR | LR11XX_SYSTEM_IRQ_CRC_ERROR;
    if (s_is_tx) {
        irq_mask |= LR11XX_SYSTEM_IRQ_TX_DONE;
    }
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_set_dio_irq_params(&s_lr, irq_mask, 0)), TAG, "irq mask");
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_system_clear_irq_status(&s_lr, LR11XX_SYSTEM_IRQ_ALL_MASK)), TAG, "clr irq");

    return ESP_OK;
}

static esp_err_t lr1121_enter_rx_continuous(void)
{
    s_last_rx_restart_us = esp_timer_get_time();
    return lr_status_to_esp(lr11xx_radio_set_rx_with_timeout_in_rtc_step(&s_lr, LR1121_RX_CONTINUOUS));
}

bool lora_radio_init(bool is_tx_role)
{
    s_is_tx = is_tx_role;
    s_params.freq_centi_mhz = CONFIG_LORA_FREQ_MHZ * 100;
    s_params.bw_khz = CONFIG_LORA_BW_KHZ;
    s_params.sf = CONFIG_LORA_SF;
    s_params.cr = CONFIG_LORA_CR;
    s_params.tx_power_dbm = 14;
    s_params.preamble_syms = 8;
    s_params.payload_len = 0;
    s_params.crc_on = true;
    s_params.iq_invert = false;
    s_params.header_implicit = false;
    sanitize_params(&s_params);

    ESP_LOGI(TAG, "Init LR1121 over SPI host %d (MOSI=%d, MISO=%d, SCK=%d, CS=%d, RST=%d, BUSY=%d, DIO1=%d)",
             CONFIG_LORA_SPI_HOST,
             CONFIG_LORA_PIN_MOSI,
             CONFIG_LORA_PIN_MISO,
             CONFIG_LORA_PIN_SCK,
             CONFIG_LORA_PIN_CS,
             CONFIG_LORA_PIN_RST,
             CONFIG_LORA_PIN_BUSY,
             CONFIG_LORA_PIN_DIO1);
    if (CONFIG_LORA_PIN_BUSY < 0) {
        ESP_LOGE(TAG, "BUSY pin обов'язковий для LR1121, вкажіть коректний GPIO");
        return false;
    }

    if (radio_setup_spi() != ESP_OK) {
        ESP_LOGE(TAG, "SPI init failed");
        return false;
    }
    if (lr1121_hw_init() != ESP_OK) {
        ESP_LOGE(TAG, "IO init failed");
        return false;
    }
    if (lr1121_system_boot() != ESP_OK) {
        ESP_LOGE(TAG, "System boot failed");
        return false;
    }
    if (lr1121_apply_lora(&s_params) != ESP_OK) {
        ESP_LOGE(TAG, "Initial params failed");
        return false;
    }

    if (!s_is_tx) {
        lr1121_enter_rx_continuous();
    }

    ESP_LOGI(TAG, "Radio ready: %.2f MHz BW %d kHz SF %d CR 4/%d TX %d dBm",
             s_params.freq_centi_mhz / 100.0f,
             s_params.bw_khz,
             s_params.sf,
             s_params.cr,
             s_params.tx_power_dbm);
    return true;
}

esp_err_t lora_radio_apply_params(const lora_params_t *params)
{
    if (!params) {
        return ESP_ERR_INVALID_ARG;
    }
    lora_params_t clean = *params;
    sanitize_params(&clean);
    esp_err_t err = lr1121_apply_lora(&clean);
    if (err == ESP_OK) {
        s_params = clean;
        if (!s_is_tx) {
            lr1121_enter_rx_continuous();
        }
        ESP_LOGI(TAG, "Apply LoRa params: %.2f MHz, BW %d kHz, SF %d, CR 4/%d, TX %d dBm",
                 s_params.freq_centi_mhz / 100.0f,
                 s_params.bw_khz,
                 s_params.sf,
                 s_params.cr,
                 s_params.tx_power_dbm);
    }
    return err;
}

esp_err_t lora_radio_set_tx_power_dbm(int dbm)
{
    lora_params_t next = s_params;
    next.tx_power_dbm = dbm;
    return lora_radio_apply_params(&next);
}

esp_err_t lora_radio_set_frequency_centi_mhz(int freq_centi_mhz)
{
    lora_params_t next = s_params;
    next.freq_centi_mhz = freq_centi_mhz;
    return lora_radio_apply_params(&next);
}

void lora_radio_get_params(lora_params_t *out)
{
    if (!out) {
        return;
    }
    *out = s_params;
}

void lora_radio_tick_tx(void)
{
    if (!s_is_tx) {
        return;
    }
    lr11xx_system_irq_mask_t irq_status = 0;
    if (lr11xx_system_get_and_clear_irq_status(&s_lr, &irq_status) != LR11XX_STATUS_OK) {
        return;
    }
    if (irq_status == 0) {
        return;
    }
    s_metrics.last_status = (uint8_t)(irq_status & 0xFF);
    if (irq_status & LR11XX_SYSTEM_IRQ_TX_DONE) {
        s_tx_in_progress = false;
        ESP_LOGI(TAG, "TX done");
    }
    if (irq_status & LR11XX_SYSTEM_IRQ_TIMEOUT) {
        s_tx_in_progress = false;
        ESP_LOGW(TAG, "TX timeout");
    }
}

void lora_radio_tick_rx(void)
{
    if (s_is_tx) {
        return;
    }

    lr11xx_system_irq_mask_t irq_status = 0;
    if (lr11xx_system_get_and_clear_irq_status(&s_lr, &irq_status) != LR11XX_STATUS_OK) {
        return;
    }
    if (irq_status == 0) {
        int64_t now = esp_timer_get_time();
        if (now - s_last_rx_restart_us > 1000000) {
            lr1121_enter_rx_continuous();
        }
        return;
    }
    s_metrics.last_status = (uint8_t)(irq_status & 0xFF);

    if (irq_status & LR11XX_SYSTEM_IRQ_RX_DONE) {
        lr11xx_radio_rx_buffer_status_t rx_status = {0};
        if (lr11xx_radio_get_rx_buffer_status(&s_lr, &rx_status) == LR11XX_STATUS_OK && rx_status.pld_len_in_bytes > 0) {
            uint8_t buf[sizeof(s_last_payload)];
            uint8_t copy_len = rx_status.pld_len_in_bytes;
            if (copy_len >= sizeof(buf)) {
                copy_len = sizeof(buf) - 1;
            }
            if (lr11xx_regmem_read_buffer8(&s_lr, buf, rx_status.buffer_start_pointer, copy_len) == LR11XX_STATUS_OK) {
                memcpy(s_last_payload, buf, copy_len);
                s_last_payload[copy_len] = '\0';
                s_last_payload_len = copy_len;

                lr11xx_radio_pkt_status_lora_t pkt = {0};
                if (lr11xx_radio_get_lora_pkt_status(&s_lr, &pkt) == LR11XX_STATUS_OK) {
                    s_metrics.rssi_dbm = pkt.rssi_pkt_in_dbm;
                    s_metrics.snr_db = pkt.snr_pkt_in_db;
                }
                ESP_LOGI(TAG, "RX %u bytes RSSI=%ddBm SNR=%ddB \"%s\"",
                         (unsigned)copy_len, s_metrics.rssi_dbm, s_metrics.snr_db, s_last_payload);
            }
        }
        lr1121_enter_rx_continuous();
    }

    if (irq_status & (LR11XX_SYSTEM_IRQ_TIMEOUT | LR11XX_SYSTEM_IRQ_HEADER_ERROR | LR11XX_SYSTEM_IRQ_CRC_ERROR)) {
        ESP_LOGW(TAG, "RX irq=0x%04X (timeout/header/crc)", irq_status);
        lr1121_enter_rx_continuous();
    }
}

void lora_radio_get_metrics(lora_metrics_t *out)
{
    if (!out) {
        return;
    }
    *out = s_metrics;
}

void lora_radio_get_last_payload(char *out, size_t out_len)
{
    if (!out || out_len == 0) {
        return;
    }
    if (s_last_payload_len == 0) {
        strlcpy(out, "(no data)", out_len);
        return;
    }
    strlcpy(out, s_last_payload, out_len);
}

esp_err_t lora_radio_send(const uint8_t *data, size_t len)
{
    if (!s_is_tx) {
        return ESP_ERR_INVALID_STATE;
    }
    if (!data || len == 0) {
        return ESP_ERR_INVALID_ARG;
    }
    if (len > 255) {
        len = 255; // обмеження довжини пакета
    }

    // Оновити pkt params під фактичну довжину
    lr11xx_radio_pkt_params_lora_t pkt = {
        .preamble_len_in_symb = s_params.preamble_syms,
        .header_type = s_params.header_implicit ? LR11XX_RADIO_LORA_PKT_IMPLICIT : LR11XX_RADIO_LORA_PKT_EXPLICIT,
        .pld_len_in_bytes = (uint8_t)len,
        .crc = s_params.crc_on ? LR11XX_RADIO_LORA_CRC_ON : LR11XX_RADIO_LORA_CRC_OFF,
        .iq = s_params.iq_invert ? LR11XX_RADIO_LORA_IQ_INVERTED : LR11XX_RADIO_LORA_IQ_STANDARD,
    };
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_lora_pkt_params(&s_lr, &pkt)), TAG, "pkt params tx");

    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_regmem_write_buffer8(&s_lr, data, len)), TAG, "write buf");

    // timeout 0 => без тайм-ауту, чекаємо TX_DONE
    ESP_RETURN_ON_ERROR(lr_status_to_esp(lr11xx_radio_set_tx(&s_lr, 0)), TAG, "set tx");
    s_tx_in_progress = true;
    s_last_tx_us = esp_timer_get_time();
    s_params.payload_len = (uint8_t)len;
    return ESP_OK;
}
