use nocontrol::{
    Operator, OperatorState, Specification,
    manifests::{Action, Manifest, ManifestMetadata, ManifestState},
};
use serde::{Deserialize, Serialize};
use tracing_test::traced_test;

#[tokio::test]
#[traced_test]
async fn test_can_run_reconciler() -> anyhow::Result<()> {
    let operator = OperatorState::new(MyOperator {});
    let mut control_plane = nocontrol::ControlPlane::new(operator);
    control_plane.with_deadline(std::time::Duration::from_secs(3));

    tokio::spawn({
        let control_plane = control_plane.clone();
        async move {
            control_plane
                .add_manifest(Manifest {
                    name: "some-manifest".into(),
                    metadata: ManifestMetadata {},
                    spec: Specifications::Deployment(DeploymentControllerManifest {
                        name: "some-name".into(),
                    }),
                })
                .await
                .unwrap();

            tokio::time::sleep(std::time::Duration::from_secs(1)).await;

            control_plane
                .add_manifest(Manifest {
                    name: "some-manifest".into(),
                    metadata: ManifestMetadata {},
                    spec: Specifications::Deployment(DeploymentControllerManifest {
                        name: "some-changed-name".into(),
                    }),
                })
                .await
                .unwrap();
        }
    });

    control_plane.execute().await?;

    Err(anyhow::anyhow!("fail"))
}

#[derive(Clone)]
pub struct MyOperator {}

impl Operator for MyOperator {
    type Specifications = Specifications;
    type Error = anyhow::Error;

    async fn reconcile(
        &self,
        desired_manifest: &mut ManifestState<Specifications>,
    ) -> anyhow::Result<Action> {
        match &desired_manifest.manifest.spec {
            Specifications::Deployment(spec) => {
                tracing::info!(
                    "reconciliation was called for name = {}, value = {}",
                    desired_manifest.manifest.name,
                    spec.name
                )
            }
        }

        Ok(Action::Requeue(std::time::Duration::from_secs(1)))
    }
}

#[derive(Clone, Serialize, Deserialize)]
pub enum Specifications {
    Deployment(DeploymentControllerManifest),
}

impl Specification for Specifications {
    fn kind(&self) -> &'static str {
        match self {
            Specifications::Deployment(_) => "deployment",
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentControllerManifest {
    name: String,
}
