use std::{sync::Arc, time::Duration};

use tokio::sync::Mutex;

use crate::Operator;

#[derive(Clone)]
pub struct OperatorState<T: Operator> {
    inner: Arc<Mutex<T>>,
    config: Arc<OperatorConfig>,
}

impl<T: Operator> OperatorState<T> {
    pub fn new(operator: T) -> Self {
        Self {
            inner: Arc::new(Mutex::new(operator)),
            config: Default::default(),
        }
    }

    pub fn new_with_config(operator: T, config: OperatorConfig) -> Self {
        Self {
            inner: Arc::new(Mutex::new(operator)),
            config: Arc::new(config),
        }
    }

    pub(crate) fn config(&self) -> &OperatorConfig {
        &self.config
    }
}

impl<T: Operator> Operator for OperatorState<T> {
    type Specifications = T::Specifications;
    type Error = T::Error;

    async fn reconcile(
        &self,
        desired_manifest: &mut crate::manifests::ManifestState<Self::Specifications>,
    ) -> Result<crate::manifests::Action, Self::Error> {
        self.inner.lock().await.reconcile(desired_manifest).await
    }

    async fn on_error(
        &self,
        desired_manifest: &mut crate::manifests::ManifestState<Self::Specifications>,
        error: &Self::Error,
    ) -> crate::manifests::Action {
        self.inner
            .lock()
            .await
            .on_error(desired_manifest, error)
            .await
    }
}

pub struct OperatorConfig {
    pub backoff_policy: BackoffPolicy,
    pub reconcile_on: Option<tokio::sync::mpsc::Receiver<()>>,
    /// Interval at which all manifests are re-enqueued for reconciliation.
    /// Default is 5 minutes.
    pub resync_interval: Duration,
}

impl Default for OperatorConfig {
    fn default() -> Self {
        Self {
            backoff_policy: BackoffPolicy::Static {
                delay: Duration::from_secs(30),
            },
            reconcile_on: Default::default(),
            resync_interval: Duration::from_secs(5 * 60),
        }
    }
}

pub enum BackoffPolicy {
    Never,
    Expotential {
        multiplier: f64,
        initial_delay: Duration,
        max_delay: Option<Duration>,
    },
    Static {
        delay: Duration,
    },
    Ramp(Vec<Duration>),
}

impl Default for BackoffPolicy {
    fn default() -> Self {
        Self::Expotential {
            multiplier: 2.0,
            initial_delay: Duration::from_secs(1),
            max_delay: Some(Duration::from_mins(5)),
        }
    }
}
