use crate::dat::Dat;
use crate::error::DatError;
use crate::manager::DatManager;
use crate::payload::DatPayload;
use reqwest::Client;
use std::sync::Arc;
use std::time::Duration;
use tokio::sync::RwLock;

pub static DAT_CMS_API_VERSION: &str = "v1";

pub struct DatCmsManager {
    url: String,
    version: RwLock<u64>,
    manager: DatManager,
    client: Client,
}

pub struct DatCmsManagerBuilder {
    https: bool,
    host: String,
    port: u16,
    interval: Duration,
}
impl DatCmsManagerBuilder {

    #[inline]
    pub fn https(mut self, https: bool) -> Self {
        self.https = https;
        self
    }

    #[inline]
    pub fn host(mut self, host: impl Into<String>) -> Self {
        self.host = host.into();
        self
    }

    #[inline]
    pub fn port(mut self, port: u16) -> Self {
        self.port = port;
        self
    }

    #[inline]
    pub fn interval(mut self, interval: Duration) -> Self {
        self.interval = interval;
        self
    }

    pub async fn build(self) -> Arc<DatCmsManager> {

        if self.host.is_empty() {
            panic!("host is empty");
        } else if self.host.contains('/') {
            panic!("invalid host: example: localhost, 192.168.1.1, dat.saro.me");
        } else if self.port == 0 {
            panic!("port is empty");
        } else if self.interval.as_secs() < 60 {
            panic!("minimum interval is 60 seconds");
        }

        let url = format!("{}://{}:{}/{}/certs", if self.https { "https" } else { "http" }, self.host, self.port, DAT_CMS_API_VERSION);

        let manager = Arc::new(DatCmsManager {
            url,
            version: RwLock::new(0),
            manager: DatManager::new(),
            client: Client::new(),
        });

        // first sync
        manager.sync().await.unwrap();

        let manager_clone = Arc::clone(&manager);

        tokio::spawn(async move {
            let mut ticker = tokio::time::interval(self.interval);
            loop {
                ticker.tick().await;
                let _ = manager_clone.sync().await.is_ok();
            }
        });

        manager
    }
}


impl Default for DatCmsManagerBuilder {
    fn default() -> Self {
        DatCmsManagerBuilder {
            https: false,
            host: "localhost".to_string(),
            port: 8088,
            interval: Duration::from_secs(60),
        }
    }
}

impl DatCmsManager {
    pub fn builder() -> DatCmsManagerBuilder {
        DatCmsManagerBuilder::default()
    }

    #[inline]
    pub fn issue(&self, plain: &str, secure: &str) -> Result<String, DatError> {
        self.manager.issue(plain, secure)
    }

    #[inline]
    pub fn parse(&self, dat: Dat) -> Result<DatPayload, DatError> {
        self.manager.parse(dat)
    }

    #[inline]
    pub fn parse_without_verify(&self, dat: Dat) -> Result<DatPayload, DatError> {
        self.manager.parse_without_verify(dat)
    }

    #[inline]
    pub fn get_manager(&self) -> &DatManager {
        &self.manager
    }

    #[inline]
    pub async fn get_version(&self) -> u64 {
        self.version.read().await.clone()
    }

    pub async fn sync(&self) -> Result<(), String> {
        let response = self.client.get(&self.url)
            .query(&[("ver", self.version.read().await.clone())])
            .send().await
            .map_err(|e| format!("connection error {}: {e:?}", self.url))
            .inspect_err(|e| {
                #[cfg(feature = "tracing")]
                tracing::error!("[CRITICAL] DAT CMS SYNC Exception: {e}")
            })?;

        let res = response.error_for_status()
            .map_err(|e| format!("response status not 200 {}: {e:?}", self.url))
            .inspect_err(|e| {
                #[cfg(feature = "tracing")]
                tracing::error!("[CRITICAL] DAT CMS SYNC Exception: {e}")
            })?;

        let cert_str = res.text().await
            .map_err(|e| format!("response does not text type {}: {e:?}", self.url))
            .inspect_err(|e| {
                #[cfg(feature = "tracing")]
                tracing::error!("[CRITICAL] DAT CMS SYNC Exception: {e}")
            })?;

        let (ver, certs) = cert_str.split_once("\n")
            .ok_or_else(|| format!("wrong response {}: {cert_str}", self.url))
            .inspect_err(|e| {
                #[cfg(feature = "tracing")]
                tracing::error!("[CRITICAL] DAT CMS SYNC Exception: {e}")
            })?;
        let ver = ver.parse::<u64>()
            .map_err(|e| format!("invalid version {}: {e:?}", self.url))
            .inspect_err(|e| {
                #[cfg(feature = "tracing")]
                tracing::error!("[CRITICAL] DAT CMS SYNC Exception: {e}")
            })?;

        let mut version_lock = self.version.write().await;
        let count = self.manager.import(&certs, true)
            .map_err(|e| format!("import error {}: {e:?}", self.url))
            .inspect_err(|e| {
                #[cfg(feature = "tracing")]
                tracing::error!("[CRITICAL] DAT CMS SYNC Exception: {e}")
            })?;
        *version_lock = ver;

        #[cfg(feature = "tracing")]
        tracing::info!("Sync OK: Renew {} DAT certificates.", count);
        Ok(())
    }
}
