use std::sync::Arc;

use log::warn;
use opnsense_config_xml::{
    Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer, OPNsense,
};

use crate::{config::OPNsenseShell, Error};

pub struct LoadBalancerConfig<'a> {
    opnsense: &'a mut OPNsense,
    opnsense_shell: Arc<dyn OPNsenseShell>,
}

impl<'a> LoadBalancerConfig<'a> {
    pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
        Self {
            opnsense,
            opnsense_shell,
        }
    }

    pub fn get_full_config(&self) -> &Option<HAProxy> {
        &self.opnsense.opnsense.haproxy
    }

    fn with_haproxy<F, R>(&mut self, f: F) -> R
    where
        F: FnOnce(&mut HAProxy) -> R,
    {
        match &mut self.opnsense.opnsense.haproxy.as_mut() {
            Some(haproxy) => f(haproxy),
            None => unimplemented!(
                "Adding a backend is not supported when haproxy config does not exist yet"
            ),
        }
    }

    pub fn enable(&mut self, enabled: bool) {
        self.with_haproxy(|haproxy| haproxy.general.enabled = enabled as i32);
    }

    pub fn add_backend(&mut self, backend: HAProxyBackend) {
        warn!("TODO make sure this new backend does not refer non-existing entities like servers or health checks");
        self.with_haproxy(|haproxy| haproxy.backends.backends.push(backend));
    }

    pub fn add_frontend(&mut self, frontend: Frontend) {
        self.with_haproxy(|haproxy| haproxy.frontends.frontend.push(frontend));
    }

    pub fn add_healthcheck(&mut self, healthcheck: HAProxyHealthCheck) {
        self.with_haproxy(|haproxy| haproxy.healthchecks.healthchecks.push(healthcheck));
    }

    pub fn add_servers(&mut self, mut servers: Vec<HAProxyServer>) {
        self.with_haproxy(|haproxy| haproxy.servers.servers.append(&mut servers));
    }

    pub async fn reload_restart(&self) -> Result<(), Error> {
        self.opnsense_shell.exec("configctl haproxy stop").await?;
        self.opnsense_shell
            .exec("configctl template reload OPNsense/HAProxy")
            .await?;
        self.opnsense_shell
            .exec("configctl template reload OPNsense/Syslog")
            .await?;
        self.opnsense_shell
            .exec("/usr/local/sbin/haproxy -c -f /usr/local/etc/haproxy.conf.staging")
            .await?;

        // This script copies the staging config to production config. I am not 100% sure it is
        // required in the context
        self.opnsense_shell
            .exec("/usr/local/opnsense/scripts/OPNsense/HAProxy/setup.sh deploy")
            .await?;

        self.opnsense_shell
            .exec("configctl haproxy configtest")
            .await?;
        self.opnsense_shell.exec("configctl haproxy start").await?;
        Ok(())
    }
}
