use serde::Serialize;

use crate::plant_hal::{BoardInteraction, HAL};
use crate::config::{PlantControllerConfig, TankConfig};

const OPEN_TANK_VOLTAGE: f32 = 3.0;
pub const WATER_FROZEN_THRESH: f32 = 4.0;

#[derive(Debug, Clone, Serialize)]
pub enum TankError {
    SensorDisabled,
    SensorMissing(f32),
    SensorValueError { value: f32, min: f32, max: f32 },
    BoardError(String),
}

pub enum TankState {
    Present(f32),
    Error(TankError),
    Disabled,
}

fn raw_voltage_to_divider_percent(raw_value_mv: f32) -> Result<f32, TankError> {
    if raw_value_mv > OPEN_TANK_VOLTAGE {
        return Err(TankError::SensorMissing(raw_value_mv));
    }

    let r2 = raw_value_mv * 50.0 / (3.3 - raw_value_mv);
    let mut percent = r2 / 190_f32 * 100_f32;
    percent = percent.clamp(0.0, 100.0);
    Ok(percent)
}

fn raw_voltage_to_tank_fill_percent(
    raw_value_mv: f32,
    config: &TankConfig,
) -> Result<f32, TankError> {
    let divider_percent = raw_voltage_to_divider_percent(raw_value_mv)?;
    if divider_percent < config.tank_empty_percent.into()
        || divider_percent > config.tank_full_percent.into()
    {
        return Err(TankError::SensorValueError {
            value: divider_percent,
            min: config.tank_empty_percent.into(),
            max: config.tank_full_percent.into(),
        });
    }
    Ok(
        (divider_percent - f32::from(config.tank_empty_percent)) * 100.
            / f32::from(config.tank_full_percent - config.tank_empty_percent),
    )
}

impl TankState {
    pub fn left_ml(&self, config: &TankConfig) -> Result<f32, TankError> {
        match self {
            TankState::Disabled => Err(TankError::SensorDisabled),
            TankState::Error(err) => Err(err.clone()),
            TankState::Present(raw_value_mv) => {
                let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
                Ok(config.tank_useable_ml as f32 * tank_fill_percent / 100.)
            }
        }
    }
    pub fn enough_water(&self, config: &TankConfig) -> Result<bool, TankError> {
        match self {
            TankState::Disabled => Err(TankError::SensorDisabled),
            TankState::Error(err) => Err(err.clone()),
            TankState::Present(raw_value_mv) => {
                let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config)?;
                if tank_fill_percent > config.tank_empty_percent.into() {
                    Ok(true)
                } else {
                    Ok(false)
                }
            }
        }
    }

    pub fn is_enabled(&self) -> bool {
        !matches!(self, TankState::Disabled)
    }

    pub fn warn_level(&self, config: &TankConfig) -> Result<bool, TankError> {
        match self {
            TankState::Disabled => Err(TankError::SensorDisabled),
            TankState::Error(err) => Err(err.clone()),
            TankState::Present(raw_value_mv) => {
                let tank_fill_percent = raw_voltage_to_tank_fill_percent(*raw_value_mv, config);
                match tank_fill_percent {
                    Ok(value) => {
                        if value < config.tank_warn_percent.into() {
                            Ok(true)
                        } else {
                            Ok(false)
                        }
                    }
                    Err(err) => match err {
                        TankError::SensorValueError { value, min, max: _ } => Ok(value < min),
                        _ => Err(err),
                    },
                }
            }
        }
    }

    pub fn got_error(&self, config: &TankConfig) -> Option<TankError> {
        match self {
            TankState::Present(raw_value_mv) => {
                raw_voltage_to_tank_fill_percent(*raw_value_mv, config).err()
            }
            TankState::Error(err) => Some(err.clone()),
            TankState::Disabled => Some(TankError::SensorDisabled),
        }
    }

    pub fn as_mqtt_info(
        &self,
        config: &TankConfig,
        water_temp: &anyhow::Result<f32>,
    ) -> TankInfo {
        let mut tank_err: Option<TankError> = None;
        let left_ml = match self.left_ml(config) {
            Err(err) => {
                tank_err = Some(err);
                None
            }
            Ok(left_ml) => Some(left_ml),
        };
        let enough_water = self.enough_water(config).unwrap_or(false); //NOTE: is this correct if there is an error assume not enough water?
        let warn_level = self.warn_level(config).unwrap_or(false); //NOTE: should warn level be triggered if there is an error?
        let raw = match self {
            TankState::Disabled | TankState::Error(_) => None,
            TankState::Present(raw_value_mv) => Some(*raw_value_mv),
        };

        let percent = match raw {
            Some(r) => raw_voltage_to_tank_fill_percent(r, config).ok(),
            None => None,
        };

        TankInfo {
            enough_water,
            warn_level,
            left_ml,
            sensor_error: tank_err,
            raw,
            water_frozen: water_temp
                .as_ref()
                .is_ok_and(|temp| *temp < WATER_FROZEN_THRESH),
            water_temp: water_temp.as_ref().copied().ok(),
            temp_sensor_error: water_temp.as_ref().err().map(|err| err.to_string()),
            percent,
        }
    }
}

pub fn determine_tank_state(
    board: &mut std::sync::MutexGuard<'_, HAL<'_>>,
    config: &PlantControllerConfig,
) -> TankState {
    if config.tank.tank_sensor_enabled {
        match board.tank_sensor_voltage() {
            Ok(raw_sensor_value_mv) => TankState::Present(raw_sensor_value_mv),
            Err(err) => TankState::Error(TankError::BoardError(err.to_string())),
        }
    } else {
        TankState::Disabled
    }
}

#[derive(Debug, Serialize)]
/// Information structure send to mqtt for monitoring purposes
pub struct TankInfo {
    /// there is enough water in the tank
    enough_water: bool,
    /// warning that water needs to be refilled soon
    warn_level: bool,
    /// estimation how many ml are still in the tank
    left_ml: Option<f32>,
    /// if there is an issue with the water level sensor
    sensor_error: Option<TankError>,
    /// raw water sensor value
    raw: Option<f32>,
    /// percent value
    percent: Option<f32>,
    /// water in the tank might be frozen
    water_frozen: bool,
    /// water temperature
    water_temp: Option<f32>,
    temp_sensor_error: Option<String>,
}
