use std::sync::Arc;

use jiff::ToSpan;
use sha2::{Digest, Sha256};
use tokio::sync::RwLock;

use crate::{
    Specification,
    manifests::{
        Manifest, ManifestChangeEvent, ManifestChangeEventType, ManifestLease, ManifestState,
        ManifestStatus, ManifestStatusState, WorkerId,
    },
};

#[derive(Clone)]
pub struct BackingStore<T: Specification> {
    manifests: Arc<RwLock<Vec<ManifestState<T>>>>,
}

impl<T: Specification> BackingStore<T> {
    pub fn new() -> Self {
        Self {
            manifests: Arc::new(RwLock::new(Vec::new())),
        }
    }

    pub async fn get_owned_and_potential_leases(&self) -> anyhow::Result<Vec<ManifestState<T>>> {
        let now = jiff::Timestamp::now().checked_sub(1.second())?;
        let manifests = self
            .manifests
            .read()
            .await
            .iter()
            .filter(|m| match &m.lease {
                Some(lease) if lease.last_seen < now => true,
                Some(_lease) => false,
                None => true,
            })
            .cloned()
            .collect::<Vec<_>>();

        Ok(manifests)
    }

    pub async fn get_manifests(&self) -> anyhow::Result<Vec<ManifestState<T>>> {
        Ok(self.manifests.read().await.clone())
    }

    pub async fn get(&self, name: &str) -> anyhow::Result<Option<ManifestState<T>>> {
        Ok(self
            .manifests
            .read()
            .await
            .iter()
            .find(|m| m.manifest.name == name)
            .cloned())
    }

    pub async fn update_lease(&self, manifest_state: &ManifestState<T>) -> anyhow::Result<()> {
        tracing::trace!(manifest_state.manifest.name, "updating lease");
        let mut manifests = self.manifests.write().await;

        match manifests
            .iter_mut()
            .find(|m| m.manifest.name == manifest_state.manifest.name)
        {
            Some(manifest) => {
                let mut manifest_state = manifest_state.clone();
                if let Some(lease) = manifest_state.lease.as_mut() {
                    lease.last_seen = jiff::Timestamp::now();
                }

                manifest.lease = manifest_state.lease
            }
            None => anyhow::bail!("manifest is not found"),
        }

        Ok(())
    }

    pub async fn acquire_lease(
        &self,
        manifest_state: &ManifestState<T>,
        worker_id: &WorkerId,
    ) -> anyhow::Result<()> {
        tracing::trace!(manifest_state.manifest.name, "acquiring lease");
        let mut manifests = self.manifests.write().await;

        match manifests
            .iter_mut()
            .find(|m| m.manifest.name == manifest_state.manifest.name)
        {
            Some(manifest) => {
                let mut manifest_state = manifest_state.clone();
                manifest_state.lease = Some(ManifestLease {
                    owner: *worker_id,
                    last_seen: jiff::Timestamp::now(),
                });
                manifest.lease = manifest_state.lease
            }
            None => anyhow::bail!("manifest is not found"),
        }

        Ok(())
    }

    pub async fn upsert_manifest(&self, manifest: Manifest<T>) -> anyhow::Result<()> {
        let mut manifests = self.manifests.write().await;

        let now = jiff::Timestamp::now();
        match manifests
            .iter_mut()
            .find(|m| m.manifest.name == manifest.name)
        {
            Some(current_manifest) => {
                tracing::debug!("updating manifest");

                current_manifest.manifest = manifest;
                current_manifest.updated = now;
            }
            None => {
                tracing::debug!("adding manifest");
                let content = serde_json::to_vec(&manifest)?;
                let output = Sha256::digest(&content);

                manifests.push(ManifestState {
                    manifest,
                    manifest_hash: output[..].to_vec(),
                    generation: 0,
                    status: ManifestStatus {
                        status: ManifestStatusState::Pending,
                        events: Vec::default(),
                        changes: vec![ManifestChangeEvent {
                            created: now,
                            handled: false,
                            event: ManifestChangeEventType::Changed,
                        }],
                    },
                    created: now,
                    updated: now,
                    lease: None,
                });
            }
        }

        Ok(())
    }

    pub async fn update_state(&self, manifest: &ManifestState<T>) -> anyhow::Result<()> {
        let mut manifests = self.manifests.write().await;

        let Some(current_manifest) = manifests
            .iter_mut()
            .find(|m| m.manifest.name == manifest.manifest.name)
        else {
            anyhow::bail!(
                "manifest state was not found for {}",
                manifest.manifest.name
            )
        };

        let manifest = manifest.clone();

        current_manifest.generation += 1;
        current_manifest.status = manifest.status;
        current_manifest.updated = manifest.updated;

        Ok(())
    }
}
