//! Pluggable backend instantiation: type-string → factory → backend.

use std::collections::HashMap;
use std::sync::Arc;

use async_trait::async_trait;

use crate::backend::ArchiveBackend;

#[derive(Debug, thiserror::Error)]
pub enum BackendBuildError {
    #[error("unknown backend type `{0}`")]
    UnknownType(String),
    #[error("invalid params for backend `{name}` (type `{type_name}`): {source}")]
    InvalidParams {
        name: String,
        type_name: String,
        #[source]
        source: anyhow::Error,
    },
    #[error("backend `{name}` (type `{type_name}`) failed to initialise: {source}")]
    BackendInit {
        name: String,
        type_name: String,
        #[source]
        source: anyhow::Error,
    },
}

#[async_trait]
pub trait BackendFactory: Send + Sync {
    fn type_name(&self) -> &'static str;

    async fn build(
        &self,
        archive_name: &str,
        params: toml::Value,
    ) -> Result<Arc<dyn ArchiveBackend>, BackendBuildError>;
}

pub struct BackendRegistry {
    factories: HashMap<&'static str, Arc<dyn BackendFactory>>,
}

impl BackendRegistry {
    pub fn new() -> Self {
        Self {
            factories: HashMap::new(),
        }
    }

    pub fn register(&mut self, factory: Arc<dyn BackendFactory>) {
        self.factories.insert(factory.type_name(), factory);
    }

    pub fn get(&self, type_name: &str) -> Option<&Arc<dyn BackendFactory>> {
        self.factories.get(type_name)
    }

    pub async fn build(
        &self,
        archive_name: &str,
        type_name: &str,
        params: toml::Value,
    ) -> Result<Arc<dyn ArchiveBackend>, BackendBuildError> {
        let factory = self
            .get(type_name)
            .ok_or_else(|| BackendBuildError::UnknownType(type_name.into()))?;
        factory.build(archive_name, params).await
    }
}

impl Default for BackendRegistry {
    fn default() -> Self {
        Self::new()
    }
}

use std::path::PathBuf;

use crate::backends::JsonlBackend;

#[derive(Debug, serde::Deserialize)]
struct JsonlParams {
    path: PathBuf,
}

pub struct JsonlFactory;

#[async_trait]
impl BackendFactory for JsonlFactory {
    fn type_name(&self) -> &'static str {
        "jsonl"
    }

    async fn build(
        &self,
        archive_name: &str,
        params: toml::Value,
    ) -> Result<Arc<dyn ArchiveBackend>, BackendBuildError> {
        let parsed: JsonlParams =
            params
                .try_into()
                .map_err(|e: toml::de::Error| BackendBuildError::InvalidParams {
                    name: archive_name.into(),
                    type_name: "jsonl".into(),
                    source: anyhow::Error::new(e),
                })?;

        let backend = JsonlBackend::new(parsed.path).await.map_err(|e| {
            BackendBuildError::BackendInit {
                name: archive_name.into(),
                type_name: "jsonl".into(),
                source: anyhow::Error::new(e),
            }
        })?;

        Ok(Arc::new(backend) as Arc<dyn ArchiveBackend>)
    }
}

impl BackendRegistry {
    /// Convenience: a registry with `jsonl` pre-registered.
    pub fn with_jsonl() -> Self {
        let mut r = Self::new();
        r.register(Arc::new(JsonlFactory));
        r
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::backend::mock::MockBackend;

    struct MockFactory;
    #[async_trait]
    impl BackendFactory for MockFactory {
        fn type_name(&self) -> &'static str {
            "mock"
        }
        async fn build(
            &self,
            _archive_name: &str,
            _params: toml::Value,
        ) -> Result<Arc<dyn ArchiveBackend>, BackendBuildError> {
            Ok(Arc::new(MockBackend::new()) as Arc<dyn ArchiveBackend>)
        }
    }

    #[tokio::test]
    async fn unknown_type_rejected() {
        let r = BackendRegistry::new();
        let err = r
            .build("a", "nope", toml::Value::Table(Default::default()))
            .await
            .map(|_| ())
            .unwrap_err();
        assert!(matches!(err, BackendBuildError::UnknownType(s) if s == "nope"));
    }

    #[tokio::test]
    async fn registered_factory_builds() {
        let mut r = BackendRegistry::new();
        r.register(Arc::new(MockFactory));
        let backend = r
            .build("a", "mock", toml::Value::Table(Default::default()))
            .await
            .unwrap();
        let _: &dyn ArchiveBackend = &*backend;
    }

    #[tokio::test]
    async fn jsonl_factory_builds_under_tempdir() {
        let dir = tempfile::tempdir().unwrap();
        let r = BackendRegistry::with_jsonl();
        let mut params = toml::value::Table::new();
        params.insert(
            "path".into(),
            toml::Value::String(dir.path().to_string_lossy().into_owned()),
        );
        let backend = r
            .build("main", "jsonl", toml::Value::Table(params))
            .await
            .unwrap();
        let health = backend.health_check().await;
        assert!(matches!(
            health,
            crate::backend::HealthStatus::Healthy | crate::backend::HealthStatus::Degraded { .. }
        ));
    }
}
