
#include <zephyr/sys/printk.h>
#include <zephyr/kernel.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/settings/settings.h>

#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/mesh.h>

#include "board.h" /* sample's board helpers (LED/buzzer/buttons) */

/* -------------------- Mesh & Test parameters -------------------- */

#define MOD_LF             0x0000

/* Addresses */
#define GROUP_ADDR         0xC000
#define PUBLISHER_ADDR     0x000F   /* Unicast address of the Node (publisher) */

/* Vendor opcodes */
#define OP_VENDOR_BUTTON   BT_MESH_MODEL_OP_3(0x00, BT_COMP_ID_LF)
#define OP_VENDOR_BEACON   BT_MESH_MODEL_OP_3(0x01, BT_COMP_ID_LF) /* 4-byte seq */

/* Beacon publish rate and logging granularity (tune as needed) */
#define PUB_INTERVAL_MS    100      /* 10 Hz */
#define LOG_EVERY_N        100      /* Print once every N RX/TX beacons */

/* -------------------- Static keys for self-provision -------------------- */
/* (Same pattern as mesh_demo; OK for in-lab testing.) */
static const uint8_t net_key[16] = {
    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
};
static const uint8_t dev_key[16] = {
    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
};
static const uint8_t app_key[16] = {
    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
    0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
};

static const uint16_t net_idx;     /* 0 */
static const uint16_t app_idx;     /* 0 */
static const uint32_t iv_index;    /* 0 */
static uint8_t flags;              /* 0 */

static uint16_t addr = NODE_ADDR;  /* Provided by board_init(); default from sample */

/* -------------------- Heartbeat callbacks -------------------- */

static void heartbeat(const struct bt_mesh_hb_sub *sub, uint8_t hops, uint16_t feat)
{
    /* Called on every received HB that matches subscription parameters */
    printk("Received heartbeat: hops=%u, feat=0x%04x\n", hops, feat);
    board_heartbeat(hops, feat);
    board_play("100H");
}

static void hb_sub_end(const struct bt_mesh_hb_sub *sub)
{
    /* Called when the Heartbeat subscription period ends */
    printk("[HB] sub_end: src=0x%04x dst=0x%04x received=%u min_hops=%u max_hops=%u\n",
           sub->src, sub->dst, sub->count, sub->min_hops, sub->max_hops);
}

/* Register Heartbeat callbacks */
BT_MESH_HB_CB_DEFINE(hb_cb) = {
    .recv    = heartbeat,
    .sub_end = hb_sub_end,
};

/* -------------------- Foundation & Health -------------------- */

static struct bt_mesh_cfg_cli cfg_cli = { };

static void attention_on(const struct bt_mesh_model *model)
{
    printk("attention_on()\n");
    board_attention(true);
    board_play("100H100C100H100C100H100C");
}

static void attention_off(const struct bt_mesh_model *model)
{
    printk("attention_off()\n");
    board_attention(false);
}

static const struct bt_mesh_health_srv_cb health_srv_cb = {
    .attn_on  = attention_on,
    .attn_off = attention_off,
};

static struct bt_mesh_health_srv health_srv = {
    .cb = &health_srv_cb,
};

BT_MESH_HEALTH_PUB_DEFINE(health_pub, 0);

/* -------------------- Vendor model ops -------------------- */

static int vnd_button_pressed(const struct bt_mesh_model *model,
                  struct bt_mesh_msg_ctx *ctx,
                  struct net_buf_simple *buf)
{
    printk("src 0x%04x\n", ctx->addr);

    if (ctx->addr == bt_mesh_model_elem(model)->rt->addr) {
        return 0;
    }

    board_other_dev_pressed(ctx->addr);
    board_play("100G200 100G");
    return 0;
}

/* Forward-declare RX handler and publisher worker */
static int vnd_beacon_rx(const struct bt_mesh_model *model,
                         struct bt_mesh_msg_ctx *ctx,
                         struct net_buf_simple *buf);
static void pub_work_handler(struct k_work *work);

static const struct bt_mesh_model_op vnd_ops[] = {
    { OP_VENDOR_BUTTON, BT_MESH_LEN_EXACT(0), vnd_button_pressed },
    { OP_VENDOR_BEACON, BT_MESH_LEN_EXACT(4), vnd_beacon_rx },
    BT_MESH_MODEL_OP_END,
};

/* -------------------- Composition -------------------- */

static const struct bt_mesh_model root_models[] = {
    BT_MESH_MODEL_CFG_SRV,
    BT_MESH_MODEL_CFG_CLI(&cfg_cli),
    BT_MESH_MODEL_HEALTH_SRV(&health_srv, &health_pub),
};

static const struct bt_mesh_model vnd_models[] = {
    BT_MESH_MODEL_VND(BT_COMP_ID_LF, MOD_LF, vnd_ops, NULL, NULL),
};

static const struct bt_mesh_elem elements[] = {
    BT_MESH_ELEM(0, root_models, vnd_models),
};

static const struct bt_mesh_comp comp = {
    .cid         = BT_COMP_ID_LF,
    .elem        = elements,
    .elem_count  = ARRAY_SIZE(elements),
};

/* -------------------- Configuration (keys, binds, subs, heartbeat) -------------------- */

static void configure(void)
{
    printk("Configuring...\n");

    /* Add Application Key */
    bt_mesh_cfg_cli_app_key_add(net_idx, addr, net_idx, app_idx, app_key, NULL);

    /* Bind to vendor model */
    bt_mesh_cfg_cli_mod_app_bind_vnd(net_idx, addr, addr, app_idx, MOD_LF, BT_COMP_ID_LF, NULL);

    /* Bind to Health model */
    bt_mesh_cfg_cli_mod_app_bind(net_idx, addr, addr, app_idx, BT_MESH_MODEL_ID_HEALTH_SRV, NULL);

    /* Add model subscription (vendor model subscribes to GROUP_ADDR) */
    bt_mesh_cfg_cli_mod_sub_add_vnd(net_idx, addr, addr, GROUP_ADDR, MOD_LF, BT_COMP_ID_LF, NULL);

#if NODE_ADDR == PUBLISHER_ADDR
    /* Configure Heartbeat publication (indefinite) on Node */
    {
        struct bt_mesh_cfg_cli_hb_pub pub = {
            .dst     = GROUP_ADDR,
            .count   = 0xff,     /* 0xff -> indefinite */
            .period  = 0x05,     /* log encoded seconds: 2^(5-1)=16 s */
            .ttl     = 0x07,
            .feat    = 0,        /* no feature-triggered publication */
            .net_idx = net_idx,
        };
        bt_mesh_cfg_cli_hb_pub_set(net_idx, addr, &pub, NULL);
        printk("Publishing heartbeat messages\n");
    }
#endif

    printk("Configuration complete\n");
    board_play("100C100D100E100F100G100A100H");
}

/* -------------------- Provisioning -------------------- */

static const uint8_t dev_uuid[16] = { 0xdd, 0xdd };

static const struct bt_mesh_prov prov = {
    .uuid = dev_uuid,
};

/* -------------------- Vendor beacon publisher (Node) -------------------- */

#if NODE_ADDR == PUBLISHER_ADDR
static struct k_work_delayable pub_work;
static uint32_t pub_seq = 0;
static uint32_t pub_sent = 0;
#endif

static void start_publisher(void)
{
#if NODE_ADDR == PUBLISHER_ADDR
    k_work_init_delayable(&pub_work, pub_work_handler);
    k_work_schedule(&pub_work, K_MSEC(PUB_INTERVAL_MS));
    printk("Vendor beacon publishing started (%u ms interval)\n", PUB_INTERVAL_MS);
#else
    printk("GW ready to receive vendor beacons\n");
#endif
}

#if NODE_ADDR == PUBLISHER_ADDR
static void pub_work_handler(struct k_work *work)
{
    NET_BUF_SIMPLE_DEFINE(msg, 3 + 4); /* 3B opcode + 4B seq */
    struct bt_mesh_msg_ctx ctx = {
        .app_idx  = app_idx,
        .addr     = GROUP_ADDR,          /* send to group (or GW unicast) */
        .send_ttl = BT_MESH_TTL_DEFAULT,
    };

    bt_mesh_model_msg_init(&msg, OP_VENDOR_BEACON);
    net_buf_simple_add_le32(&msg, pub_seq);

    int err = bt_mesh_model_send(&vnd_models[0], &ctx, &msg, NULL, NULL);
    if (err) {
        printk("[PUB] send err %d (seq=%u)\n", err, pub_seq);
    } else {
        pub_sent++;
        if ((pub_sent % LOG_EVERY_N) == 0) {
            printk("[PUB] sent=%u last_seq=%u dst=0x%04x\n",
                   pub_sent, pub_seq, ctx.addr);
        }
        pub_seq++;
    }

    k_work_schedule(&pub_work, K_MSEC(PUB_INTERVAL_MS));
}
#endif

/* -------------------- Vendor beacon receiver (GW) -------------------- */

#if NODE_ADDR != PUBLISHER_ADDR
static uint32_t rx_count = 0;
static uint32_t drop_count = 0;
static bool first_seq_seen = false;
static uint32_t last_seq = 0;
#endif

static int vnd_beacon_rx(const struct bt_mesh_model *model,
                         struct bt_mesh_msg_ctx *ctx,
                         struct net_buf_simple *buf)
{
#if NODE_ADDR != PUBLISHER_ADDR
    if (buf->len < 4) {
        printk("Beacon too short\n");
        return 0;
    }

    uint32_t seq_rx = sys_get_le32(buf->data);

    if (!first_seq_seen) {
        first_seq_seen = true;
        last_seq = seq_rx;
    } else {
        uint32_t expected_next = last_seq + 1;

        if (seq_rx != expected_next) {
            uint32_t missed;
            if (seq_rx > expected_next) {
                missed = seq_rx - expected_next;
            } else {
                /* 32-bit wrap-around: missing = (2^32 - expected_next) + seq_rx */
                missed = (UINT32_MAX - expected_next + 1U) + seq_rx;
            }
            drop_count += missed;
        }
        last_seq = seq_rx;
    }

    rx_count++;
    uint32_t total = rx_count + drop_count;

    if ((rx_count % LOG_EVERY_N) == 0) {
        /* Print integer percent with two decimals using permyriad */
        uint32_t loss_permyriad = total ? (drop_count * 10000U / total) : 0U;
        printk("[GW] rx=%u drops=%u total=%u loss=%u.%02u%% last_seq=%u src=0x%04x ttl=%u rssi=%d dBm\n",
               rx_count, drop_count, total,
               loss_permyriad / 100, loss_permyriad % 100,
               last_seq, ctx->addr, ctx->recv_ttl, ctx->recv_rssi);
    }
#else
    ARG_UNUSED(model); ARG_UNUSED(ctx); ARG_UNUSED(buf);
#endif
    return 0;
}

/* -------------------- Mesh bring-up -------------------- */

static void bt_ready(int err)
{
    if (err) {
        printk("Bluetooth init failed (err %d)\n", err);
        return;
    }

    printk("Bluetooth initialized\n");

    err = bt_mesh_init(&prov, &comp);
    if (err) {
        printk("Initializing mesh failed (err %d)\n", err);
        return;
    }

    printk("Mesh initialized\n");

    if (IS_ENABLED(CONFIG_BT_SETTINGS)) {
        printk("Loading stored settings\n");
        settings_load();
    }

    err = bt_mesh_provision(net_key, net_idx, flags, iv_index, addr, dev_key);
    if (err == -EALREADY) {
        printk("Using stored settings\n");
        configure();
    } else if (err) {
        printk("Provisioning failed (err %d)\n", err);
        return;
    } else {
        printk("Provisioning completed\n");
        configure();
    }

#if NODE_ADDR != PUBLISHER_ADDR
    /* Heartbeat subscription must be explicitly set each boot (time-limited) */
    {
        struct bt_mesh_cfg_cli_hb_sub sub = {
            .src    = PUBLISHER_ADDR,
            .dst    = GROUP_ADDR,
            .period = 0x10, /* log-encoded seconds (choose as you prefer) */
        };
        bt_mesh_cfg_cli_hb_sub_set(net_idx, addr, &sub, NULL);
        printk("Subscribing to heartbeat messages\n");
    }
#endif

    /* Start vendor beacon publisher if this node is the publisher */
    start_publisher();
}

/* -------------------- Button -> Vendor BUTTON demo (unchanged) -------------------- */

static uint16_t target = GROUP_ADDR;

void board_button_1_pressed(void)
{
    NET_BUF_SIMPLE_DEFINE(msg, 3 + 4);
    struct bt_mesh_msg_ctx ctx = {
        .app_idx  = app_idx,
        .addr     = target,
        .send_ttl = BT_MESH_TTL_DEFAULT,
    };

    bt_mesh_model_msg_init(&msg, OP_VENDOR_BUTTON);

    if (bt_mesh_model_send(&vnd_models[0], &ctx, &msg, NULL, NULL)) {
        printk("Unable to send Vendor Button message\n");
    }

    printk("Button message sent with OpCode 0x%08x\n", OP_VENDOR_BUTTON);
}

uint16_t board_set_target(void)
{
    switch (target) {
    case GROUP_ADDR:
        target = 1U;
        break;
    case 9:
        target = GROUP_ADDR;
        break;
    default:
        target++;
        break;
    }
    return target;
}

/* -------------------- Board "tune" player (from sample) -------------------- */

static K_SEM_DEFINE(tune_sem, 0, 1);
static const char *tune_str;

void board_play(const char *str)
{
    tune_str = str;
    k_sem_give(&tune_sem);
}

/* -------------------- Main -------------------- */

int main(void)
{
    int err;

    printk("Initializing...\n");

    err = board_init(&addr);
    if (err) {
        printk("Board initialization failed\n");
        return 0;
    }

    printk("Unicast address: 0x%04x\n", addr);

    /* Initialize the Bluetooth Subsystem */
    err = bt_enable(bt_ready);
    if (err) {
        printk("Bluetooth init failed (err %d)\n", err);
        return 0;
    }

    while (1) {
        k_sem_take(&tune_sem, K_FOREVER);
        board_play_tune(tune_str);
    }

    return 0;
}
