/* Copyright 2023-2025 Bruce Merry
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, either version 3 of the License, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <https://www.gnu.org/licenses/>.
 */

use async_trait::async_trait;

pub type Error = Box<dyn std::error::Error + Send + Sync>;
pub type Result<T> = std::result::Result<T, Error>;

#[derive(PartialEq, Debug, Clone, Default)]
pub struct Info {
    pub capacity: f64,     // Wh
    pub export_power: f64, // W
    pub export_enabled: bool,
}

#[derive(PartialEq, Debug)]
pub struct CoilInfo {
    /// Reading at the CT coil (W) - positive for import from grid
    pub coil: f64,
    /// Reading at the inverter (W) - positive for import from grid
    pub inverter: f64,
    /// Whether the trickle setting applies to the coil
    pub coil_active: bool,
}

#[async_trait]
pub trait Inverter: Send {
    async fn get_info(&mut self) -> Result<Info>;
    async fn get_soc(&mut self) -> Result<f64>;
    async fn get_coil(&mut self) -> Result<Option<CoilInfo>>;
    /// Battery charge plus grid export (W)
    async fn get_net_production(&mut self) -> Result<f64>;
    async fn set_min_soc(&mut self, target: f64, fallback: f64) -> Result<()>;
    async fn set_trickle(&mut self, trickle: f64) -> Result<()>;
    async fn set_full_export(&mut self, enable: bool) -> Result<()>;
}

/// Wrap another inverter class to turn set methods into nops
pub struct DryrunInverter<T: Inverter> {
    base: T,
}

impl<T: Inverter> DryrunInverter<T> {
    pub fn new(base: T) -> Self {
        Self { base }
    }
}

#[async_trait]
impl<T: Inverter> Inverter for DryrunInverter<T> {
    async fn get_info(&mut self) -> Result<Info> {
        self.base.get_info().await
    }

    async fn get_soc(&mut self) -> Result<f64> {
        self.base.get_soc().await
    }

    async fn get_net_production(&mut self) -> Result<f64> {
        self.base.get_net_production().await
    }

    async fn get_coil(&mut self) -> Result<Option<CoilInfo>> {
        self.base.get_coil().await
    }

    async fn set_min_soc(&mut self, _target: f64, _fallback: f64) -> Result<()> {
        Ok(())
    }

    async fn set_trickle(&mut self, _trickle: f64) -> Result<()> {
        Ok(())
    }

    async fn set_full_export(&mut self, _enable: bool) -> Result<()> {
        Ok(())
    }
}

#[cfg(test)]
pub(crate) mod test {
    use super::*;
    use async_trait::async_trait;

    /// An inverter made available for test fixtures
    #[derive(Default)]
    pub struct TestInverter {
        pub info: Info,
        pub target_soc: f64,
        pub fallback_soc: f64,
        pub soc: f64,
        pub net_production: f64,
        pub trickle: f64,
        pub full_export: bool,
        pub inject_error: Option<Error>, // Error returned on next call (one-shot)
    }

    impl TestInverter {
        pub fn new() -> Self {
            TestInverter {
                info: Info {
                    capacity: 5000.0,
                    export_power: 3450.0,
                    export_enabled: true,
                },
                soc: 75.0,
                ..Default::default()
            }
        }

        fn check_inject_error(&mut self) -> Result<()> {
            self.inject_error.take().map_or(Ok(()), |err| Err(err))
        }
    }

    #[async_trait]
    impl Inverter for TestInverter {
        async fn get_info(&mut self) -> Result<Info> {
            self.check_inject_error()?;
            Ok(self.info.clone())
        }

        async fn get_soc(&mut self) -> Result<f64> {
            self.check_inject_error()?;
            Ok(self.soc)
        }

        async fn get_net_production(&mut self) -> Result<f64> {
            self.check_inject_error()?;
            Ok(self.net_production)
        }

        async fn set_min_soc(&mut self, target: f64, fallback: f64) -> Result<()> {
            self.check_inject_error()?;
            self.target_soc = target;
            self.fallback_soc = fallback;
            Ok(())
        }

        async fn get_coil(&mut self) -> Result<Option<CoilInfo>> {
            self.check_inject_error()?;
            Ok(Some(CoilInfo {
                coil: 450.0,
                inverter: 200.0,
                coil_active: true,
            }))
        }

        async fn set_trickle(&mut self, trickle: f64) -> Result<()> {
            self.check_inject_error()?;
            self.trickle = trickle;
            Ok(())
        }

        async fn set_full_export(&mut self, enable: bool) -> Result<()> {
            self.check_inject_error()?;
            self.full_export = enable;
            Ok(())
        }
    }

    #[tokio::test]
    async fn test_dry_run_inverter() {
        let mut base = TestInverter::new();
        base.target_soc = 10.0;
        base.fallback_soc = 20.0;
        base.net_production = 200.0;
        base.trickle = 40.0;
        let mut inverter = DryrunInverter::new(base);
        // Test getters
        assert_eq!(
            inverter.get_info().await.unwrap(),
            Info {
                capacity: 5000.0,
                export_power: 3450.0,
                export_enabled: true,
            }
        );
        assert_eq!(inverter.get_soc().await.unwrap(), 75.0);
        assert_eq!(inverter.get_net_production().await.unwrap(), 200.0);
        assert_eq!(
            inverter.get_coil().await.unwrap(),
            Some(CoilInfo {
                coil: 450.0,
                inverter: 200.0,
                coil_active: true
            })
        );
        // Test that setters have no effect
        inverter.set_min_soc(5.0, 6.0).await.unwrap();
        assert_eq!(inverter.base.target_soc, 10.0);
        assert_eq!(inverter.base.fallback_soc, 20.0);
        inverter.set_trickle(100.0).await.unwrap();
        assert_eq!(inverter.base.trickle, 40.0);
        inverter.set_full_export(true).await.unwrap();
        assert_eq!(inverter.base.full_export, false);
    }
}
