use chrono::Timelike;
use serde::{Deserialize, Serialize};
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio_serial::{SerialPort, SerialPortBuilderExt};

use crate::gauges::{
    BATTERY_TEMP, BATTERY_VOLTAGE, CHARGE_STATE, INPUT_CURRENT, PL_DUTY_CYCLE, PL_LOAD_CURRENT,
    TARGET_VOLTAGE,
};

pub struct Pli {
    friendly_name: String,
    port: tokio_serial::SerialStream,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
pub struct PlState {
    pub battery_voltage: f64,
    pub target_voltage: f64,
    pub duty_cycle: f64,
    pub internal_charge_current: f64,
    pub internal_load_current: f64,
    pub battery_temp: f64,
    pub regulator_state: RegulatorState,
    // pub internal_charge_ah_accumulator: u16,
    // pub external_charge_ah_accumulator: u16,
    // pub internal_load_ah_accumulator: u16,
    // pub external_load_ah_accumulator: u16,
    // pub internal_charge_ah: u16,
    // pub external_charge_ah: u16,
    // pub internal_load_ah: u16,
    // pub external_load_ah: u16,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub enum RegulatorState {
    Boost,
    Equalise,
    Absorption,
    Float,
}

impl From<u8> for RegulatorState {
    fn from(value: u8) -> Self {
        match value & 0b11 {
            0b00 => Self::Boost,
            0b01 => Self::Equalise,
            0b10 => Self::Absorption,
            0b11 => Self::Float,
            _ => unreachable!(),
        }
    }
}

impl From<RegulatorState> for u8 {
    fn from(value: RegulatorState) -> Self {
        match value {
            RegulatorState::Boost => 0b00,
            RegulatorState::Equalise => 0b01,
            RegulatorState::Absorption => 0b10,
            RegulatorState::Float => 0b11,
        }
    }
}

impl Default for RegulatorState {
    fn default() -> Self {
        Self::Absorption
    }
}

fn set_regulator_gauges(state: RegulatorState, label: &str) {
    let boost = CHARGE_STATE.with_label_values(&[label, "boost"]);
    let equalise = CHARGE_STATE.with_label_values(&[label, "equalise"]);
    let absorption = CHARGE_STATE.with_label_values(&[label, "absorption"]);
    let float = CHARGE_STATE.with_label_values(&[label, "float"]);

    match state {
        RegulatorState::Boost => {
            boost.set(1);
            equalise.set(0);
            absorption.set(0);
            float.set(0);
        }
        RegulatorState::Equalise => {
            boost.set(0);
            equalise.set(1);
            absorption.set(0);
            float.set(0);
        }
        RegulatorState::Absorption => {
            boost.set(0);
            equalise.set(0);
            absorption.set(1);
            float.set(0);
        }
        RegulatorState::Float => {
            boost.set(0);
            equalise.set(0);
            absorption.set(0);
            float.set(1);
        }
    }
}

#[expect(dead_code, reason = "writing settings is not yet implemented")]
#[derive(Debug, Clone, Copy)]
pub enum PliRequest {
    ReadRam(u8),
    ReadEeprom(u8),
    SyncTime,
    SetRegulatorState(RegulatorState),
}

impl Pli {
    pub fn new(
        serial_port: &str,
        friendly_name: &str,
        baud_rate: u32,
        timeout: u64,
    ) -> Result<Self, tokio_serial::Error> {
        let port = tokio_serial::new(serial_port, baud_rate)
            .timeout(std::time::Duration::from_millis(timeout))
            .open_native_async()?;

        Ok(Self {
            friendly_name: friendly_name.to_owned(),
            port,
        })
    }

    pub async fn refresh(&mut self) -> eyre::Result<crate::controller::CommonData> {
        let new_state = self.read_state().await?;
        BATTERY_VOLTAGE
            .with_label_values(&[&self.friendly_name])
            .set(new_state.battery_voltage);
        TARGET_VOLTAGE
            .with_label_values(&[&self.friendly_name])
            .set(new_state.target_voltage);
        PL_DUTY_CYCLE
            .with_label_values(&[&self.friendly_name])
            .set(new_state.duty_cycle);
        INPUT_CURRENT
            .with_label_values(&[&self.friendly_name])
            .set(new_state.internal_charge_current);
        PL_LOAD_CURRENT
            .with_label_values(&[&self.friendly_name])
            .set(new_state.internal_load_current);
        BATTERY_TEMP
            .with_label_values(&[&self.friendly_name])
            .set(new_state.battery_temp);

        set_regulator_gauges(new_state.regulator_state, &self.friendly_name);

        Ok(crate::controller::CommonData {
            battery_voltage: new_state.battery_voltage,
            target_voltage: new_state.target_voltage,
            battery_temp: new_state.battery_temp,
        })
    }

    #[expect(dead_code, reason = "writing settings is not yet implemented")]
    pub async fn process_request(&mut self, message: PliRequest) -> eyre::Result<()> {
        match message {
            PliRequest::ReadRam(address) => {
                let data = self.read_ram_with_retires(address).await?;
                log::warn!("Read RAM at {address}: data {data}");
            }
            PliRequest::ReadEeprom(address) => {
                let data = self.read_eeprom(address).await?;
                log::warn!("Read EEPROM at {address}: data {data}");
            }
            PliRequest::SyncTime => {
                let now = chrono::Local::now();
                let timestamp = (((now.hour() * 10) + (now.minute() / 6)) & 0xFF) as u8;
                let min = (now.minute() % 6) as u8;
                let sec = (now.second() / 2).min(29) as u8;
                self.write_ram(PlRamAddress::Hour, timestamp).await?;
                self.write_ram(PlRamAddress::Min, min).await?;
                self.write_ram(PlRamAddress::Sec, sec).await?;
                log::warn!(
                    "Set time: {now} corresponds to {timestamp} + minutes {min} + seconds {sec}"
                );
            }
            PliRequest::SetRegulatorState(state) => {
                log::warn!("Setting regulator state to {state:?}");
                self.write_ram(PlRamAddress::Rstate, state.into()).await?;
            }
        }
        Ok(())
    }

    async fn read_state(&mut self) -> eyre::Result<PlState> {
        // let int_charge_acc_low = self.read_ram(PlRamAddress::Ciacc1).await?;
        // let int_charge_acc = self.read_ram(PlRamAddress::Ciacc2).await?;
        // let int_charge_acc_high = self.read_ram(PlRamAddress::Ciacc3).await?;
        // let mut internal_charge_ah_accumulator = u16::from(int_charge_acc_high) << 9;
        // internal_charge_ah_accumulator |= u16::from(int_charge_acc) << 1;
        // internal_charge_ah_accumulator |= u16::from(int_charge_acc_low & 0b1);
        // let int_charge_low = self.read_ram(PlRamAddress::Ciahl).await?;
        // let int_charge_high = self.read_ram(PlRamAddress::Ciahh).await?;
        // let internal_charge_ah = u16::from_le_bytes([int_charge_low, int_charge_high]);

        // let int_load_acc_low = self.read_ram(PlRamAddress::Liacc1).await?;
        // let int_load_acc = self.read_ram(PlRamAddress::Liacc2).await?;
        // let int_load_acc_high = self.read_ram(PlRamAddress::Liacc3).await?;
        // let mut internal_load_ah_accumulator = u16::from(int_load_acc_high) << 9;
        // internal_load_ah_accumulator |= u16::from(int_load_acc) << 1;
        // internal_load_ah_accumulator |= u16::from(int_load_acc_low & 0b1);
        // let int_load_low = self.read_ram(PlRamAddress::Liahl).await?;
        // let int_load_high = self.read_ram(PlRamAddress::Liahh).await?;
        // let internal_load_ah = u16::from_le_bytes([int_load_low, int_load_high]);

        // let ext_charge_acc_low = self.read_ram(PlRamAddress::Ceacc1).await?;
        // let ext_charge_acc = self.read_ram(PlRamAddress::Ceacc2).await?;
        // let ext_charge_acc_high = self.read_ram(PlRamAddress::Ceacc3).await?;
        // let mut external_charge_ah_accumulator = u16::from(ext_charge_acc_high) << 9;
        // external_charge_ah_accumulator |= u16::from(ext_charge_acc) << 1;
        // external_charge_ah_accumulator |= u16::from(ext_charge_acc_low & 0b1);
        // let ext_charge_low = self.read_ram(PlRamAddress::Ceahl).await?;
        // let ext_charge_high = self.read_ram(PlRamAddress::Ceahh).await?;
        // let external_charge_ah = u16::from_le_bytes([ext_charge_low, ext_charge_high]);

        // let ext_load_acc_low = self.read_ram(PlRamAddress::Leacc1).await?;
        // let ext_load_acc = self.read_ram(PlRamAddress::Leacc2).await?;
        // let ext_load_acc_high = self.read_ram(PlRamAddress::Leacc3).await?;
        // let mut external_load_ah_accumulator = u16::from(ext_load_acc_high) << 9;
        // external_load_ah_accumulator |= u16::from(ext_load_acc) << 1;
        // external_load_ah_accumulator |= u16::from(ext_load_acc_low & 0b1);
        // let ext_load_low = self.read_ram(PlRamAddress::Leahl).await?;
        // let ext_load_high = self.read_ram(PlRamAddress::Leahh).await?;
        // let external_load_ah = u16::from_le_bytes([ext_load_low, ext_load_high]);

        Ok(PlState {
            battery_voltage: f64::from(self.read_ram_with_retires(PlRamAddress::Batv).await?)
                * (4. / 10.),
            target_voltage: f64::from(self.read_ram_with_retires(PlRamAddress::Vreg).await?)
                * (4. / 10.),
            duty_cycle: f64::from(self.read_ram_with_retires(PlRamAddress::Dutycyc).await?) / 255.,
            internal_charge_current: f64::from(
                self.read_ram_with_retires(PlRamAddress::Cint).await?,
            ) * (4. / 10.),
            internal_load_current: f64::from(self.read_ram_with_retires(PlRamAddress::Lint).await?)
                * (2. / 10.),
            battery_temp: f64::from(self.read_ram_with_retires(PlRamAddress::Battemp).await?),
            regulator_state: self
                .read_ram_with_retires(PlRamAddress::Rstate)
                .await?
                .into(),
            // internal_charge_ah_accumulator,
            // external_charge_ah_accumulator,
            // internal_load_ah_accumulator,
            // external_load_ah_accumulator,
            // internal_charge_ah,
            // external_charge_ah,
            // internal_load_ah,
            // external_load_ah,
        })
    }

    async fn send_command(&mut self, req: [u8; 4]) -> eyre::Result<()> {
        self.flush()?;
        self.port.write_all(&req).await?;
        Ok(())
    }

    fn flush(&mut self) -> eyre::Result<()> {
        self.port.clear(tokio_serial::ClearBuffer::All)?;
        Ok(())
    }

    async fn receive<const LENGTH: usize>(&mut self) -> eyre::Result<[u8; LENGTH]> {
        let mut buf = [0; LENGTH];
        self.port.read_exact(&mut buf).await?;
        Ok(buf)
    }

    async fn read_ram_with_retires<T>(&mut self, address: T) -> eyre::Result<u8>
    where
        T: Into<u8>,
    {
        const READ_TRIES: usize = 3;
        let address: u8 = address.into();

        let mut last_err = None;
        for _ in 0..READ_TRIES {
            match self.read_ram_single(address).await {
                Ok(v) => return Ok(v),
                Err(e) => last_err = Some(e),
            }
        }

        Err(last_err.unwrap_or(eyre::eyre!(
            "no error was stored in read_ram_with_retries: this should be unreachable??"
        )))
    }

    async fn read_ram_single<T>(&mut self, address: T) -> eyre::Result<u8>
    where
        T: Into<u8>,
    {
        self.send_command(command(20, address.into(), 0)).await?;
        let buf = self.receive::<1>().await?;
        if buf[0] == 200 {
            Ok(self.receive::<1>().await?[0])
        } else {
            Err(eyre::eyre!("read error: result is {}", buf[0]))
        }
    }

    async fn write_ram<T>(&mut self, address: T, data: u8) -> eyre::Result<()>
    where
        T: Into<u8>,
    {
        self.send_command(command(152, address.into(), data))
            .await?;
        Ok(())
    }

    async fn read_eeprom<T>(&mut self, address: T) -> eyre::Result<u8>
    where
        T: Into<u8>,
    {
        self.send_command(command(72, address.into(), 0)).await?;
        let buf = self.receive::<1>().await?;
        if buf[0] == 200 {
            Ok(self.receive::<1>().await?[0])
        } else {
            Err(eyre::eyre!("read error: result is {}", buf[0]))
        }
    }
}

#[allow(dead_code)]
enum PlRamAddress {
    Dutycyc,
    Sec,
    Min,
    Hour,
    Batv,
    Battemp,
    Rstate,
    Vreg,
    Cint,
    Lint,
    Ciacc1,
    Ciacc2,
    Ciacc3,
    Ciahl,
    Ciahh,
    Ceacc1,
    Ceacc2,
    Ceacc3,
    Ceahl,
    Ceahh,
    Liacc1,
    Liacc2,
    Liacc3,
    Liahl,
    Liahh,
    Leacc1,
    Leacc2,
    Leacc3,
    Leahl,
    Leahh,
}

impl From<PlRamAddress> for u8 {
    fn from(value: PlRamAddress) -> Self {
        match value {
            PlRamAddress::Dutycyc => 39,
            PlRamAddress::Sec => 46,
            PlRamAddress::Min => 47,
            PlRamAddress::Hour => 48,
            PlRamAddress::Batv => 50,
            PlRamAddress::Battemp => 52,
            PlRamAddress::Rstate => 101,
            PlRamAddress::Vreg => 105,
            PlRamAddress::Cint => 213,
            PlRamAddress::Lint => 217,
            PlRamAddress::Ciacc1 => 0xB9,
            PlRamAddress::Ciacc2 => 0xBA,
            PlRamAddress::Ciacc3 => 0xBB,
            PlRamAddress::Ciahl => 0xBC,
            PlRamAddress::Ciahh => 0xBD,
            PlRamAddress::Ceacc1 => 0xBE,
            PlRamAddress::Ceacc2 => 0xBF,
            PlRamAddress::Ceacc3 => 0xC0,
            PlRamAddress::Ceahl => 0xC1,
            PlRamAddress::Ceahh => 0xC2,
            PlRamAddress::Liacc1 => 0xC3,
            PlRamAddress::Liacc2 => 0xC4,
            PlRamAddress::Liacc3 => 0xC5,
            PlRamAddress::Liahl => 0xC6,
            PlRamAddress::Liahh => 0xC7,
            PlRamAddress::Leacc1 => 0xC8,
            PlRamAddress::Leacc2 => 0xC9,
            PlRamAddress::Leacc3 => 0xCA,
            PlRamAddress::Leahl => 0xCB,
            PlRamAddress::Leahh => 0xCC,
        }
    }
}

fn command(operation: u8, address: u8, data: u8) -> [u8; 4] {
    [operation, address, data, !operation]
}