#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/uuid.h>
#include <zephyr/bluetooth/gatt.h>
#include <zephyr/settings/settings.h>
#include <lasertag_utils.h>
#include <thread_mgmt.h>
#include <ble_mgmt.h>

LOG_MODULE_REGISTER(ble_mgmt, CONFIG_BLE_MGMT_LOG_LEVEL);

/**
 * Base UUID: 03afe2cf-6c64-4a22-9289-c3ae820cXXXX
 * Alias positions: Byte 12 & 13
 */

#define LT_UUID_BASE_VAL \
	BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c0000)

/* ==========================================================================
   SERVICE 1: PROVISIONING (0x10XX)
   ========================================================================== */
#define BT_UUID_LT_PROV_SERVICE BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1000))

#define BT_UUID_LT_PROV_NAME_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1001))
#define BT_UUID_LT_PROV_PANID_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1002))
#define BT_UUID_LT_PROV_CHAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1003))
#define BT_UUID_LT_PROV_EXTPAN_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1004))
#define BT_UUID_LT_PROV_NETKEY_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1005))
#define BT_UUID_LT_PROV_NETNAME_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1006))
#define BT_UUID_LT_PROV_NODES_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1007))
#define BT_UUID_LT_PROV_TYPE_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c1008))

/* ==========================================================================
   SERVICE 2: GAME (0x20XX)
   ========================================================================== */
#define BT_UUID_LT_GAME_SERVICE BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2000))

#define BT_UUID_LT_GAME_CONFIG_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2001))
#define BT_UUID_LT_GAME_COMMAND_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2002))
#define BT_UUID_LT_GAME_LOG_DATA_CHAR BT_UUID_DECLARE_128(BT_UUID_128_ENCODE(0x03afe2cf, 0x6c64, 0x4a22, 0x9289, 0xc3ae820c2003))

/* --- Global Variables --- */
static uint8_t device_role = 0; // Store device type for provisioning

/* --- GATT Callbacks --- */

static ssize_t read_lasertag_val(struct bt_conn *conn, const struct bt_gatt_attr *attr,
								 void *buf, uint16_t len, uint16_t offset)
{
	const char *val_ptr = NULL;
	size_t val_len = 0;

	if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_TYPE_CHAR) == 0)
	{
		val_ptr = (char *)&device_role;
		val_len = sizeof(device_role);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0)
	{
		val_ptr = lasertag_get_device_name();
		val_len = strlen(val_ptr);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0)
	{
		static uint16_t pan_id;
		pan_id = lasertag_get_thread_pan_id();
		val_ptr = (char *)&pan_id;
		val_len = sizeof(pan_id);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_CHAN_CHAR) == 0)
	{
		static uint8_t chan;
		chan = lasertag_get_thread_channel();
		val_ptr = (char *)&chan;
		val_len = sizeof(chan);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_EXTPAN_CHAR) == 0)
	{
		val_ptr = (char *)lasertag_get_thread_ext_pan_id();
		val_len = 8;
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETKEY_CHAR) == 0)
	{
		val_ptr = (char *)lasertag_get_thread_network_key();
		val_len = 16;
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETNAME_CHAR) == 0)
	{
		val_ptr = lasertag_get_thread_network_name();
		val_len = strlen(val_ptr);
	}

	return bt_gatt_attr_read(conn, attr, buf, len, offset, val_ptr, val_len);
}

static ssize_t write_lasertag_val(struct bt_conn *conn, const struct bt_gatt_attr *attr,
								  const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
	int rc = 0;
	if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NAME_CHAR) == 0)
	{
		rc = lasertag_set_device_name(buf, len);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_PANID_CHAR) == 0)
	{
		if (len != 2)
			return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
		rc = lasertag_set_thread_pan_id(*(uint16_t *)buf);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_CHAN_CHAR) == 0)
	{
		if (len != 1)
			return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
		rc = lasertag_set_thread_channel(*(uint8_t *)buf);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_EXTPAN_CHAR) == 0)
	{
		if (len != 8)
			return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
		rc = lasertag_set_thread_ext_pan_id(buf);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETKEY_CHAR) == 0)
	{
		if (len != 16)
			return BT_GATT_ERR(BT_ATT_ERR_INVALID_ATTRIBUTE_LEN);
		rc = lasertag_set_thread_network_key(buf);
	}
	else if (bt_uuid_cmp(attr->uuid, BT_UUID_LT_PROV_NETNAME_CHAR) == 0)
	{
		rc = lasertag_set_thread_network_name(buf, len);
	}

	if (rc)
		return BT_GATT_ERR(BT_ATT_ERR_UNLIKELY);
	return len;
}

static ssize_t read_discovered_nodes(struct bt_conn *conn, const struct bt_gatt_attr *attr,
									 void *buf, uint16_t len, uint16_t offset)
{
	const char *list = thread_mgmt_get_discovered_list();
	return bt_gatt_attr_read(conn, attr, buf, len, offset, list, strlen(list));
}

static ssize_t write_discover_cmd(struct bt_conn *conn, const struct bt_gatt_attr *attr,
								  const void *buf, uint16_t len, uint16_t offset, uint8_t flags)
{
	/* If anything is written, trigger discovery in Thread Mesh */
	thread_mgmt_discover_nodes();
	return len;
}

/* Service Definition */
BT_GATT_SERVICE_DEFINE(provisioning_svc,
					   BT_GATT_PRIMARY_SERVICE(BT_UUID_LT_PROV_SERVICE),

					   /* Device name */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NAME_CHAR,
											  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
											  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
											  read_lasertag_val, write_lasertag_val, NULL),

					   /* Device Type (Read-only) */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_TYPE_CHAR,
											  BT_GATT_CHRC_READ,
											  BT_GATT_PERM_READ,
											  read_lasertag_val, NULL, NULL),
					   /* Thread PAN ID */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_PANID_CHAR,
											  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
											  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
											  read_lasertag_val, write_lasertag_val, NULL),

					   /* Thread Channel */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_CHAN_CHAR,
											  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
											  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
											  read_lasertag_val, write_lasertag_val, NULL),

					   /* Extended PAN ID */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_EXTPAN_CHAR,
											  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
											  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
											  read_lasertag_val, write_lasertag_val, NULL),

					   /* Network Key */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NETKEY_CHAR,
											  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
											  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
											  read_lasertag_val, write_lasertag_val, NULL),

					   /* Thread Network Name */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NETNAME_CHAR,
											  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
											  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
											  read_lasertag_val, write_lasertag_val, NULL),

					   /* Node List / Discovery Trigger */
					   BT_GATT_CHARACTERISTIC(BT_UUID_LT_PROV_NODES_CHAR,
											  BT_GATT_CHRC_READ | BT_GATT_CHRC_WRITE,
											  BT_GATT_PERM_READ | BT_GATT_PERM_WRITE,
											  read_discovered_nodes, write_discover_cmd, NULL), );

static uint8_t mfg_data[] = { 0xff, 0xff, 0x00 }; // Last byte for device role
static const struct bt_data ad[] = {
    BT_DATA_BYTES(BT_DATA_FLAGS, (BT_LE_AD_GENERAL | BT_LE_AD_NO_BREDR)),
    BT_DATA_BYTES(BT_DATA_UUID128_ALL, 
        0x00, 0x10, 0x0c, 0x82, 0xae, 0xc3, 0x89, 0x92, 
        0x22, 0x4a, 0x64, 0x6c, 0xcf, 0xe2, 0xaf, 0x03),
    BT_DATA(BT_DATA_MANUFACTURER_DATA, mfg_data, sizeof(mfg_data)), 
};

int ble_mgmt_init(uint8_t device_type)
{
	device_role = device_type;

	int err = bt_enable(NULL);
	if (err)
		return err;
	LOG_INF("Bluetooth initialized");
	return 0;
}

int ble_mgmt_adv_start(void)
{
	const char set_device_role = device_role;
	mfg_data[2] = set_device_role; // Update device role in advertising data

	const char *name = lasertag_get_device_name();
	bt_set_name(name);

	struct bt_data dynamic_sd[] = {
		BT_DATA(BT_DATA_NAME_COMPLETE, name, strlen(name)),
	};

	struct bt_le_adv_param adv_param = {
		.id = BT_ID_DEFAULT,
		.options = (BT_LE_ADV_OPT_CONN | BT_LE_ADV_OPT_SCANNABLE),
		.interval_min = BT_GAP_ADV_FAST_INT_MIN_2,
		.interval_max = BT_GAP_ADV_FAST_INT_MAX_2,
	};

	int err = bt_le_adv_start(&adv_param, ad, ARRAY_SIZE(ad), dynamic_sd, ARRAY_SIZE(dynamic_sd));
	if (!err)
	{
		LOG_INF("Advertising started as: %s, type: %d", name, device_role);
	}
	return err;
}

int ble_mgmt_adv_stop(void)
{
	int err = bt_le_adv_stop();
	if (!err)
	{
		LOG_INF("Advertising stopped");
	}
	return err;
}