use btleplug::api::{Central, Peripheral as _, WriteType};
use btleplug::platform::{Adapter, Peripheral};
use uuid::{Uuid, uuid};

const FIRMWARE_VERSION_UUID: Uuid = uuid!("00002a26-0000-1000-8000-00805f9b34fb");
const BATTERY_LEVEL_UUID: Uuid = uuid!("00002a19-0000-1000-8000-00805f9b34fb");
const HEART_RATE_UUID: Uuid = uuid!("00002a37-0000-1000-8000-00805f9b34fb");
const STEP_COUNT_UUID: Uuid = uuid!("00030001-78fc-48fe-8e23-433b3a1942d0");
const NOTIFICATION_UUID: Uuid = uuid!("00002a46-0000-1000-8000-00805f9b34fb");
const WEATHER_SERVICE_UUID: Uuid = uuid!("00050001-78fc-48fe-8e23-433b3a1942d0");
const CURRENT_TIME_UUID: Uuid = uuid!("00002a2b-0000-1000-8000-00805f9b34fb");
const NAVIGATION_FLAG_UUID: Uuid = uuid!("00010001-78fc-48fe-8e23-433b3a1942d0");
const NAVIGATION_NARRATIVE_UUID: Uuid = uuid!("00010002-78fc-48fe-8e23-433b3a1942d0");
const NAVIGATION_MAN_DIST_UUID: Uuid = uuid!("00010003-78fc-48fe-8e23-433b3a1942d0");
const NAVIGATION_PROGRESS_UUID: Uuid = uuid!("00010004-78fc-48fe-8e23-433b3a1942d0");

pub enum NotificationCategory {
    SimpleAlert = 0,
    Email,
    News,
    CallNotification,
    MissedCall,
    SMS,
    Voicemail,
    Schedule,
    HighPrioritizedAlert,
    InstantMessage,
}
pub struct Notification {
    category: NotificationCategory,
    title: String,
    body: String,
}
impl Notification {
    pub fn new(category: NotificationCategory, title: String, body: String) -> Notification {
        return Self {
            category: category,
            title: title,
            body: body,
        };
    }
}

pub async fn find_infinitime(adapter: &Adapter) -> Option<Peripheral> {
    for p in adapter.peripherals().await.unwrap() {
        if p.properties()
            .await
            .unwrap()
            .unwrap()
            .local_name
            .iter()
            .any(|name| name.to_lowercase().contains("infinitime"))
        {
            return Some(p);
        }
    }
    return None;
}

async fn get_property_value(infinitime: &Peripheral, property: Uuid) -> Vec<u8> {
    let chars = infinitime.characteristics();
    let cmd_char = chars.iter().find(|c| c.uuid == property).unwrap();
    let result = infinitime.read(cmd_char).await.unwrap();

    return result;
}

pub async fn get_firmware_version(infinitime: &Peripheral) -> String {
    let data = get_property_value(infinitime, FIRMWARE_VERSION_UUID).await;
    return String::from_utf8(data).expect("Found invalid UTF-8");
}
pub async fn get_battery_level(infinitime: &Peripheral) -> String {
    let data = get_property_value(infinitime, BATTERY_LEVEL_UUID).await;
    let percent = data[0];
    return percent.to_string();
}
pub async fn get_heart_rate(infinitime: &Peripheral) -> String {
    let data = get_property_value(infinitime, HEART_RATE_UUID).await;
    let rate = data[1];
    return rate.to_string();
}
pub async fn get_step_count(infinitime: &Peripheral) -> String {
    let data = get_property_value(infinitime, STEP_COUNT_UUID).await;
    let count = data[0];
    return count.to_string();
}
pub async fn send_notification(infinitime: &Peripheral, notification: Notification) {
    let chars = infinitime.characteristics();
    let cmd_char = chars.iter().find(|c| c.uuid == NOTIFICATION_UUID).unwrap();
    infinitime
        .write(
            cmd_char,
            &[
                &[0, notification.category as u8],
                notification.title.as_bytes(),
                notification.body.as_bytes(),
            ]
            .join(&0),
            WriteType::WithoutResponse,
        )
        .await
        .unwrap();
}

pub async fn send_current_weather(
    infinitime: &Peripheral,
    timestamp: u64,
    temp: i16,
    min_temp: i16,
    max_temp: i16,
    location: &str,
    icon: u8,
    sunrise: i16,
    sunset: i16,
) -> Result<(), btleplug::Error> {
    let chars = infinitime.characteristics();
    let cmd_char = chars
        .iter()
        .find(|c| c.uuid == WEATHER_SERVICE_UUID)
        .unwrap();

    let mut data = Vec::new();

    // Message header
    data.push(0); // Message type: Current weather
    data.push(1); // Message version: 1 (with sunrise/sunset)

    data.extend(timestamp.to_ne_bytes());

    // Temperatures (°C * 100)
    data.extend((temp * 100).to_ne_bytes());
    data.extend((min_temp * 100).to_ne_bytes());
    data.extend((max_temp * 100).to_ne_bytes());

    // Location ,padded with 0 bytes
    let location_bytes = location.as_bytes();
    for byte in location_bytes {
        data.push(*byte);
    }
    while data.len() < 48 {
        data.push(0);
    }

    data.push(icon);

    data.extend(sunrise.to_ne_bytes());

    data.extend(sunset.to_ne_bytes());

    infinitime
        .write(cmd_char, &data, WriteType::WithoutResponse)
        .await?;
    Ok(())
}

pub async fn send_forecast(
    infinitime: &Peripheral,
    timestamp: u64,
    num_days: u8,
    days: &[(i16, i16, u8)],
) -> Result<(), btleplug::Error> {
    let chars = infinitime.characteristics();
    let cmd_char = chars
        .iter()
        .find(|c| c.uuid == WEATHER_SERVICE_UUID)
        .unwrap();

    let mut data = Vec::new();

    // Message header
    data.push(1); // Message type: Forecast
    data.push(0); // Message version: 0

    data.extend(timestamp.to_ne_bytes());

    data.push(num_days);

    for (min_temp, max_temp, icon) in days {
        data.extend((min_temp * 100).to_ne_bytes());
        data.extend((max_temp * 100).to_ne_bytes());
        data.push(*icon);
    }

    infinitime
        .write(cmd_char, &data, WriteType::WithoutResponse)
        .await?;
    Ok(())
}

pub async fn send_current_time(
    infinitime: &Peripheral,
    year: u16,
    month: u8,
    day: u8,
    hour: u8,
    minute: u8,
    second: u8,
    weekday: u8,
    microsecond: u32,
) -> Result<(), btleplug::Error> {
    let chars = infinitime.characteristics();
    let cmd_char = chars.iter().find(|c| c.uuid == CURRENT_TIME_UUID).unwrap();

    let mut data = Vec::new();

    data.extend(year.to_le_bytes());
    data.push(month);
    data.push(day);
    data.push(hour);
    data.push(minute);
    data.push(second);
    data.push(weekday);
    data.push((microsecond / (1_000_000 * 256)) as u8);
    data.push(0x01);

    infinitime
        .write(cmd_char, &data, WriteType::WithoutResponse)
        .await?;
    Ok(())
}

pub struct NavigationInstruction {
    flag: String,
    narrative: String,
    man_dist: String,
    progress: u8,
}

impl NavigationInstruction {
    pub fn new(flag: String, narrative: String, man_dist: String, progress: u8) -> Self {
        Self {
            flag,
            narrative,
            man_dist,
            progress,
        }
    }
}

pub async fn send_navigation(
    infinitime: &Peripheral,
    navigation: &NavigationInstruction,
) -> Result<(), btleplug::Error> {
    let chars = infinitime.characteristics();

    // Icon name
    let cmd_char = chars
        .iter()
        .find(|c| c.uuid == NAVIGATION_FLAG_UUID)
        .unwrap();
    infinitime
        .write(
            cmd_char,
            navigation.flag.as_bytes(),
            WriteType::WithoutResponse,
        )
        .await?;

    // Text description
    let cmd_char = chars
        .iter()
        .find(|c| c.uuid == NAVIGATION_NARRATIVE_UUID)
        .unwrap();
    infinitime
        .write(
            cmd_char,
            navigation.narrative.as_bytes(),
            WriteType::WithoutResponse,
        )
        .await?;

    // Man dist
    let cmd_char = chars
        .iter()
        .find(|c| c.uuid == NAVIGATION_MAN_DIST_UUID)
        .unwrap();
    infinitime
        .write(
            cmd_char,
            navigation.man_dist.as_bytes(),
            WriteType::WithoutResponse,
        )
        .await?;

    // Progress (0-100)
    let cmd_char = chars
        .iter()
        .find(|c| c.uuid == NAVIGATION_PROGRESS_UUID)
        .unwrap();
    infinitime
        .write(cmd_char, &[navigation.progress], WriteType::WithoutResponse)
        .await?;

    Ok(())
}