use std::{fs, time::Duration};

use derive_builder::Builder;
use serde::Deserialize;

use crate::{
    error::{ErrorCode, KErr},
    logging::{logging_utils::LoggingUtils, KameleoonLogger},
    utils::{domain_validator::DomainValidator, KRes},
    warn_log,
};

const SECONDS_IN_MINUTE: u64 = 60;

const DEFAULT_REFRESH_INTERVAL_MINUTES: u64 = 60;
const DEFAULT_SESSION_DURATION_MINUTES: u64 = 30;
const DEFAULT_DEFAULT_TIMEOUT_MILLIS: u64 = 10_000;
const DEFAULT_TRACKING_INTERVAL_MILLIS: u64 = 1_000;
const MIN_TRACKING_INTERVAL_MILLIS: u64 = 1_000;
const MAX_TRACKING_INTERVAL_MILLIS: u64 = 5_000;

const DEFAULT_REFRESH_INTERVAL: Duration = Duration::from_secs(DEFAULT_REFRESH_INTERVAL_MINUTES * SECONDS_IN_MINUTE);
const DEFAULT_SESSION_DURATION: Duration = Duration::from_secs(DEFAULT_SESSION_DURATION_MINUTES * SECONDS_IN_MINUTE);
const DEFAULT_DEFAULT_TIMEOUT: Duration = Duration::from_millis(DEFAULT_DEFAULT_TIMEOUT_MILLIS);
const DEFAULT_TRACKING_INTERVAL: Duration = Duration::from_millis(DEFAULT_TRACKING_INTERVAL_MILLIS);
const MIN_MINUTE_DURATION: Duration = Duration::from_secs(SECONDS_IN_MINUTE);
const MIN_TRACKING_INTERVAL: Duration = Duration::from_millis(MIN_TRACKING_INTERVAL_MILLIS);
const MAX_TRACKING_INTERVAL: Duration = Duration::from_millis(MAX_TRACKING_INTERVAL_MILLIS);

#[derive(Builder, Default)]
pub struct KameleoonClientConfig {
    #[builder(setter(into))]
    pub client_id: String,
    #[builder(setter(into))]
    pub client_secret: String,
    #[builder(default = DEFAULT_REFRESH_INTERVAL)]
    pub refresh_interval: Duration,
    #[builder(default = DEFAULT_SESSION_DURATION)]
    pub session_duration: Duration,
    #[builder(default = DEFAULT_DEFAULT_TIMEOUT)]
    pub default_timeout: Duration,
    #[builder(default = DEFAULT_TRACKING_INTERVAL)]
    pub tracking_interval: Duration,
    #[builder(setter(into), default = "None")]
    pub proxy_host: Option<String>,
    #[builder(setter(into), default = "None")]
    pub environment: Option<String>,
    #[builder(setter(into), default = "None")]
    pub top_level_domain: Option<String>,
    #[builder(setter(into), default = "None")]
    pub network_domain: Option<String>,
}

impl KameleoonClientConfig {
    pub fn default() -> KameleoonClientConfig {
        KameleoonClientConfig {
            refresh_interval: DEFAULT_REFRESH_INTERVAL,
            session_duration: DEFAULT_SESSION_DURATION,
            default_timeout: DEFAULT_DEFAULT_TIMEOUT,
            tracking_interval: DEFAULT_TRACKING_INTERVAL,
            ..Default::default()
        }
    }

    pub fn read_from_file(path: &str) -> Option<KameleoonClientConfig> {
        let content = fs::read_to_string(path).ok()?;
        let model: KameleoonClientConfigModel = serde_json::from_str(&content).ok()?;
        Some(KameleoonClientConfig::from(model))
    }

    pub fn validate(&self) -> KRes<()> {
        if self.client_id.is_empty() {
            return Err(KErr::new(ErrorCode::ConfigCredentialsInvalid, "Client ID is empty"));
        }
        if self.client_secret.is_empty() {
            return Err(KErr::new(ErrorCode::ConfigCredentialsInvalid, "Client secret is empty"));
        }
        if self.top_level_domain.as_ref().map_or(true, |tld| tld.is_empty()) {
            KameleoonLogger::warn(
                "Setting parameter 'top_level_domain' is strictly recommended. \
                Otherwise you may have problems when using subdomains.",
            );
        }
        return Ok(());
    }

    pub(crate) fn correct(mut self) -> Self {
        self.correct_refresh_interval();
        self.correct_session_duration();
        self.correct_tracking_interval();
        self.top_level_domain = DomainValidator::validate_top_level_domain(self.top_level_domain);
        self.network_domain = DomainValidator::validate_top_level_domain(self.network_domain);
        self
    }

    fn correct_refresh_interval(&mut self) {
        if self.refresh_interval < MIN_MINUTE_DURATION {
            warn_log!(
                "Configuration refresh interval must be at least 1 minute. Default refresh interval ({} minutes) is applied.",
                DEFAULT_REFRESH_INTERVAL_MINUTES
            );
            self.refresh_interval = DEFAULT_REFRESH_INTERVAL;
        }
    }

    fn correct_session_duration(&mut self) {
        if self.session_duration < MIN_MINUTE_DURATION {
            warn_log!(
                "Session duration must be at least 1 minute. Default session duration ({} minutes) is applied.",
                DEFAULT_SESSION_DURATION_MINUTES
            );
            self.session_duration = DEFAULT_SESSION_DURATION;
        }
    }

    fn correct_tracking_interval(&mut self) {
        if self.tracking_interval > MAX_TRACKING_INTERVAL {
            warn_log!(
                "Tracking interval must not be longer than {:?}. Maximum possible interval was applied.",
                MAX_TRACKING_INTERVAL
            );
            self.tracking_interval = MAX_TRACKING_INTERVAL;
        } else if self.tracking_interval < MIN_TRACKING_INTERVAL {
            warn_log!(
                "Tracking interval must not be shorter than {:?}. Minimum possible interval was applied.",
                MIN_TRACKING_INTERVAL
            );
            self.tracking_interval = MIN_TRACKING_INTERVAL;
        }
    }
}

impl From<KameleoonClientConfigModel> for KameleoonClientConfig {
    fn from(model: KameleoonClientConfigModel) -> Self {
        let refresh_interval = model
            .refresh_interval_minutes
            .map_or(DEFAULT_REFRESH_INTERVAL, |m| Duration::from_secs(m * SECONDS_IN_MINUTE));
        let session_duration = model
            .session_duration_minutes
            .map_or(DEFAULT_SESSION_DURATION, |m| Duration::from_secs(m * SECONDS_IN_MINUTE));
        let default_timeout = model.default_timeout_millis.map_or(DEFAULT_DEFAULT_TIMEOUT, Duration::from_millis);
        let tracking_interval = model.tracking_interval_millis.map_or(DEFAULT_TRACKING_INTERVAL, Duration::from_millis);

        Self {
            refresh_interval,
            session_duration,
            default_timeout,
            tracking_interval,
            proxy_host: model.proxy_host,
            environment: model.environment,
            client_id: model.client_id,
            client_secret: model.client_secret,
            top_level_domain: model.top_level_domain,
            network_domain: model.network_domain,
        }
    }
}

impl std::fmt::Debug for KameleoonClientConfig {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "KameleoonClientConfig{{client_id:{},client_secret:{},refresh_interval:{:?},\
        session_duration:{:?},default_timeout:{:?},environment:{},proxy_host:{},\
        top_level_domain:{}}}",
            LoggingUtils::secret(Some(&self.client_id)),
            LoggingUtils::secret(Some(&self.client_secret)),
            self.refresh_interval,
            self.session_duration,
            self.default_timeout,
            self.environment.as_deref().unwrap_or(""),
            self.proxy_host.as_deref().unwrap_or(""),
            self.top_level_domain.as_deref().unwrap_or("")
        )
    }
}

#[derive(Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
struct KameleoonClientConfigModel {
    refresh_interval_minutes: Option<u64>,
    session_duration_minutes: Option<u64>,
    default_timeout_millis: Option<u64>,
    tracking_interval_millis: Option<u64>,
    proxy_host: Option<String>,
    environment: Option<String>,
    client_id: String,
    client_secret: String,
    top_level_domain: Option<String>,
    network_domain: Option<String>,
}
