use tokio_util::sync::CancellationToken;

use crate::{
    Operator, OperatorState,
    control_plane::{backing_store::BackingStore, reconciler::Reconciler},
    manifests::{Manifest, ManifestState},
};

pub mod backing_store;
pub mod reconciler;

#[derive(Clone)]
pub struct ControlPlane<TOperator: Operator> {
    reconciler: Reconciler<TOperator>,
    worker_id: uuid::Uuid,
    store: BackingStore<TOperator::Specifications>,

    deadline: Option<std::time::Duration>,
}

impl<TOperator: Operator> ControlPlane<TOperator> {
    pub fn new(operator: OperatorState<TOperator>) -> Self {
        let worker_id = uuid::Uuid::now_v7();
        let store = BackingStore::<TOperator::Specifications>::new();

        let reconciler = Reconciler::new(worker_id, &store, operator);

        Self {
            reconciler,
            worker_id,
            deadline: None,
            store,
        }
    }

    pub fn with_deadline(&mut self, deadline: std::time::Duration) -> &mut Self {
        self.deadline = Some(deadline);

        self
    }

    pub async fn execute(&self) -> anyhow::Result<()> {
        tracing::info!(worker_id = %self.worker_id, "starting control plane");

        let cancellation_token = CancellationToken::new();
        let child_token = cancellation_token.child_token();
        if let Some(deadline) = self.deadline {
            tokio::spawn(async move {
                tokio::time::sleep(deadline).await;
                cancellation_token.cancel();
            });
        }

        self.reconciler.reconcile(&child_token).await?;

        Ok(())
    }

    pub async fn add_manifest(
        &self,
        manifest: Manifest<TOperator::Specifications>,
    ) -> anyhow::Result<()> {
        tracing::info!(manifest.name, "adding manifest");

        self.store.upsert_manifest(manifest).await?;

        Ok(())
    }

    pub async fn get_manifests(
        &self,
    ) -> anyhow::Result<Vec<ManifestState<TOperator::Specifications>>> {
        self.store.get_manifests().await
    }
}
