use serde::Serialize;
use std::{collections::HashMap, sync::Mutex};
use strum::EnumIter;
use strum_macros::IntoStaticStr;

use esp_idf_svc::systime::EspSystemTime;
use once_cell::sync::Lazy;
use ringbuffer::{ConstGenericRingBuffer, RingBuffer};
use text_template::Template;
use unit_enum::UnitEnum;

const TXT_SHORT_LENGTH: usize = 8;
const TXT_LONG_LENGTH: usize = 32;

const BUFFER_SIZE: usize = 220;

#[link_section = ".rtc.data"]
static mut BUFFER: ConstGenericRingBuffer<LogEntry, BUFFER_SIZE> =
    ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new();
#[allow(static_mut_refs)]
static BUFFER_ACCESS: Lazy<Mutex<&mut ConstGenericRingBuffer<LogEntry, BUFFER_SIZE>>> =
    Lazy::new(|| unsafe { Mutex::new(&mut BUFFER) });

#[derive(Serialize, Debug, Clone)]
pub struct LogEntry {
    pub timestamp: u64,
    pub message_id: u16,
    pub a: u32,
    pub b: u32,
    pub txt_short: heapless::String<TXT_SHORT_LENGTH>,
    pub txt_long: heapless::String<TXT_LONG_LENGTH>,
}

pub fn init() {
    unsafe {
        BUFFER = ConstGenericRingBuffer::<LogEntry, BUFFER_SIZE>::new();
    };
    let mut access = BUFFER_ACCESS.lock().unwrap();
    access.drain().for_each(|_| {});
}

fn limit_length<const LIMIT: usize>(input: &str, target: &mut heapless::String<LIMIT>) {
    for char in input.chars() {
        match target.push(char) {
            Ok(_) => {} //continue adding chars
            Err(_) => {
                //clear space for two asci chars
                while target.len() + 2 >= LIMIT {
                    target.pop().unwrap();
                }
                //add .. to shortened strings
                target.push('.').unwrap();
                target.push('.').unwrap();
                return;
            }
        }
    }
}

pub fn get_log() -> Vec<LogEntry> {
    let buffer = BUFFER_ACCESS.lock().unwrap();
    let mut read_copy = Vec::new();
    for entry in buffer.iter() {
        let copy = entry.clone();
        read_copy.push(copy);
    }
    drop(buffer);
    return read_copy;
}

pub fn log(message_key: LogMessage, number_a: u32, number_b: u32, txt_short: &str, txt_long: &str) {
    let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
    let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();

    limit_length(txt_short, &mut txt_short_stack);
    limit_length(txt_long, &mut txt_long_stack);

    let time = EspSystemTime {}.now().as_millis() as u64;

    let ordinal = message_key.ordinal() as u16;
    let template_string: &str = message_key.into();

    let mut values: HashMap<&str, &str> = HashMap::new();
    let number_a_str = number_a.to_string();
    let number_b_str = number_b.to_string();

    values.insert("number_a", &number_a_str);
    values.insert("number_b", &number_b_str);
    values.insert("txt_short", txt_short);
    values.insert("txt_long", txt_long);

    let template = Template::from(template_string);
    let serial_entry = template.fill_in(&values);

    println!("{serial_entry}");

    let entry = LogEntry {
        timestamp: time,
        message_id: ordinal,
        a: number_a,
        b: number_b,
        txt_short: txt_short_stack,
        txt_long: txt_long_stack,
    };

    let mut buffer = BUFFER_ACCESS.lock().unwrap();
    buffer.push(entry);
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn within_limit() {
        let test = "12345678";

        let mut txt_short_stack: heapless::String<TXT_SHORT_LENGTH> = heapless::String::new();
        let mut txt_long_stack: heapless::String<TXT_LONG_LENGTH> = heapless::String::new();
        limit_length(test, &mut txt_short_stack);
        limit_length(test, &mut txt_long_stack);

        assert_eq!(txt_short_stack.as_str(), test);
        assert_eq!(txt_long_stack.as_str(), test);
    }
}

#[derive(IntoStaticStr, EnumIter, Serialize, PartialEq, Eq, PartialOrd, Ord, Clone, UnitEnum)]
pub enum LogMessage {
    #[strum(
        serialize = "Reset due to ${txt_long} requires rtc clear ${number_a} and force config mode ${number_b}"
    )]
    ResetReason,
    #[strum(serialize = "Current restart to conf mode ${number_a}")]
    RestartToConfig,
    #[strum(serialize = "Current low voltage detection is ${number_a}")]
    LowVoltage,
    #[strum(serialize = "Error communicating with battery!! ${txt_long}")]
    BatteryCommunicationError,
    #[strum(serialize = "Tank water level cricial! Refill tank!")]
    TankWaterLevelLow,
    #[strum(serialize = "Tank sensor hardware error: ${txt_long}")]
    TankSensorBoardError,
    #[strum(serialize = "Tank sensor not present, raw voltage measured = ${number_a} mV")]
    TankSensorMissing,
    #[strum(
        serialize = "Tank sensor value out of range, min = ${number_a}%, max = ${number_b}%, value = ${text_short}%"
    )]
    TankSensorValueRangeError,
    #[strum(
        serialize = "raw measure unscaled ${number_a} hz ${number_b}, plant ${txt_short} sensor ${txt_long}"
    )]
    RawMeasure,
    #[strum(serialize = "IP info: ${txt_long}")]
    WifiInfo,
    #[strum(serialize = "Plant:${txt_short} a:${number_a} b:${number_b}")]
    TestSensor,
    #[strum(serialize = "Stay alive topic is ${txt_long}")]
    StayAlive,
    #[strum(serialize = "Connecting mqtt ${txt_short} with id ${txt_long}")]
    MqttInfo,
    #[strum(serialize = "Received stay alive with value ${txt_short}")]
    MqttStayAliveRec,
    #[strum(serialize = "Unknown topic recieved ${txt_long}")]
    UnknownTopic,
    #[strum(serialize = "Partition state is ${txt_long}")]
    PartitionState,
    #[strum(serialize = "Mounted Filesystem free ${number_a} total ${number_b} use ${txt_short}")]
    FilesystemMount,
    #[strum(
        serialize = "Mounting Filesystem, this will format the first time and needs quite some time!"
    )]
    MountingFilesystem,
    #[strum(serialize = "Year inplausible, force config mode")]
    YearInplausibleForceConfig,
    #[strum(serialize = "Going to config mode, due to request from prior run")]
    ConfigModeSoftwareOverride,
    #[strum(serialize = "Going to config mode, due to request via config mode button")]
    ConfigModeButtonOverride,
    #[strum(serialize = "Going to normal mode")]
    NormalRun,
    #[strum(serialize = "Missing normal config, entering config mode ${txt_long}")]
    ConfigModeMissingConfig,
    #[strum(serialize = "startup state wifi ${number_a} sntp ${number_b} mqtt ${txt_short}")]
    StartupInfo,
    #[strum(
        serialize = "Trying to pump for ${number_b}s with pump ${number_a} now dryrun: ${txt_short}"
    )]
    PumpPlant,
    #[strum(serialize = "Enable main power dryrun: ${number_a}")]
    EnableMain,
    #[strum(
        serialize = "Pumped multiple times, but plant is still to try attempt: ${number_a} limit :: ${number_b} plant: ${txt_short}"
    )]
    ConsecutivePumpCountLimit,
}

#[derive(Serialize)]
pub struct MessageTranslation {
    msg_type: LogMessage,
    message: &'static str,
}

impl From<&LogMessage> for MessageTranslation {
    fn from(value: &LogMessage) -> Self {
        Self {
            msg_type: value.clone(),
            message: value.into(),
        }
    }
}

impl LogMessage {
    pub fn to_log_localisation_config() -> Vec<MessageTranslation> {
        Vec::from_iter((0..LogMessage::len()).map(|i| {
            let msg_type = LogMessage::from_ordinal(i).unwrap();
            (&msg_type).into()
        }))
    }
}
