//! Python bindings for storage backends

use crate::models::{PyDecisionSnapshot, PySnapshot, PySnapshotQuery};
use crate::runtime::{get_runtime, PythonAsyncExt};
use briefcase_core::storage::{
    buffered::BufferedBackend, LakeFSBackend, SqliteBackend, StorageBackend,
};
use pyo3::prelude::*;
use pyo3::types::PyList;
use std::sync::Arc;

/// Python wrapper for BufferedBackend
#[pyclass(name = "BufferedBackend")]
pub struct PyBufferedBackend {
    pub inner: Arc<BufferedBackend>,
}

#[pymethods]
impl PyBufferedBackend {
    #[new]
    fn new(backend: PyObject, buffer_size: usize) -> PyResult<Self> {
        Python::with_gil(|py| {
            let inner: Arc<dyn StorageBackend> =
                if let Ok(sqlite) = backend.bind(py).extract::<PyRef<PySqliteBackend>>() {
                    Arc::new(sqlite.inner.clone())
                } else if let Ok(lakefs) = backend.bind(py).extract::<PyRef<PyLakeFSBackend>>() {
                    Arc::new(lakefs.inner.clone())
                } else {
                    return Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
                        "Invalid backend type",
                    ));
                };

            // BufferedBackend::new calls tokio::spawn internally, which requires
            // a Tokio runtime context on the calling thread.  We use block_on so
            // the current thread enters the runtime before construction.
            let runtime = get_runtime()?;
            let buffered = runtime.block_on(async { BufferedBackend::new(inner, buffer_size) });
            Ok(Self {
                inner: Arc::new(buffered),
            })
        })
    }

    /// Save a decision snapshot
    fn save_decision(&self, decision: PyRef<PyDecisionSnapshot>) -> PyResult<String> {
        let backend = self.inner.clone();
        let decision_inner = decision.inner.clone();
        backend.save_decision(&decision_inner).block_on_python()
    }
}

/// Python wrapper for SqliteBackend
#[pyclass(name = "SqliteBackend")]
pub struct PySqliteBackend {
    pub inner: SqliteBackend,
}

#[pymethods]
impl PySqliteBackend {
    #[new]
    fn new(path: Option<String>) -> PyResult<Self> {
        let backend = if let Some(path) = path {
            SqliteBackend::new(path)
        } else {
            SqliteBackend::in_memory()
        };

        match backend {
            Ok(backend) => Ok(Self { inner: backend }),
            Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
                "Failed to create SQLite backend: {}",
                e
            ))),
        }
    }

    #[classmethod]
    fn in_memory(_cls: &Bound<'_, pyo3::types::PyType>) -> PyResult<Self> {
        SqliteBackend::in_memory()
            .map(|inner| Self { inner })
            .map_err(|e| PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(e.to_string()))
    }

    fn save(&self, snapshot: PyRef<PySnapshot>) -> PyResult<String> {
        self.inner.save(&snapshot.inner).block_on_python()
    }

    fn save_decision(&self, decision: PyRef<PyDecisionSnapshot>) -> PyResult<String> {
        self.inner.save_decision(&decision.inner).block_on_python()
    }

    fn load(&self, snapshot_id: String) -> PyResult<PySnapshot> {
        self.inner
            .load(&snapshot_id)
            .block_on_python()
            .map(|s| PySnapshot { inner: s })
    }

    fn load_decision(&self, decision_id: String) -> PyResult<PyDecisionSnapshot> {
        self.inner
            .load_decision(&decision_id)
            .block_on_python()
            .map(|d| PyDecisionSnapshot { inner: d })
    }

    fn query(&self, query: PyRef<PySnapshotQuery>) -> PyResult<PyObject> {
        let snapshots = self.inner.query(query.inner.clone()).block_on_python()?;
        Python::with_gil(|py| {
            let list = PyList::empty(py);
            for s in snapshots {
                list.append(Py::new(py, PySnapshot { inner: s })?)?;
            }
            Ok(list.into())
        })
    }

    fn delete(&self, id: String) -> PyResult<bool> {
        self.inner.delete(&id).block_on_python()
    }
    fn health_check(&self) -> PyResult<bool> {
        self.inner.health_check().block_on_python()
    }
    fn __repr__(&self) -> String {
        "SqliteBackend()".into()
    }
}

/// Python wrapper for LakeFSBackend
#[pyclass(name = "LakeFSBackend")]
pub struct PyLakeFSBackend {
    pub inner: LakeFSBackend,
}

#[pymethods]
impl PyLakeFSBackend {
    #[new]
    fn new(
        endpoint: String,
        repository: String,
        branch: String,
        access_key: String,
        secret_key: String,
    ) -> PyResult<Self> {
        let config = briefcase_core::storage::LakeFSConfig::new(
            endpoint, repository, branch, access_key, secret_key,
        );
        match LakeFSBackend::new(config) {
            Ok(b) => Ok(Self { inner: b }),
            Err(e) => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
                e.to_string(),
            )),
        }
    }

    fn save(&self, snapshot: PyRef<PySnapshot>) -> PyResult<String> {
        self.inner.save(&snapshot.inner).block_on_python()
    }
    fn save_decision(&self, decision: PyRef<PyDecisionSnapshot>) -> PyResult<String> {
        self.inner.save_decision(&decision.inner).block_on_python()
    }
    fn load(&self, id: String) -> PyResult<PySnapshot> {
        self.inner
            .load(&id)
            .block_on_python()
            .map(|s| PySnapshot { inner: s })
    }
    fn load_decision(&self, id: String) -> PyResult<PyDecisionSnapshot> {
        self.inner
            .load_decision(&id)
            .block_on_python()
            .map(|d| PyDecisionSnapshot { inner: d })
    }
    fn delete(&self, id: String) -> PyResult<bool> {
        self.inner.delete(&id).block_on_python()
    }
    fn health_check(&self) -> PyResult<bool> {
        self.inner.health_check().block_on_python()
    }
    fn flush(&self) -> PyResult<String> {
        let res = self.inner.flush().block_on_python()?;
        Ok(format!("Flushed {} snapshots", res.snapshots_written))
    }
}

/// Unified storage backend enum for Python bindings
#[derive(Clone)]
pub enum PyStorageBackend {
    Sqlite(SqliteBackend),
    LakeFS(LakeFSBackend),
}

#[async_trait::async_trait]
impl StorageBackend for PyStorageBackend {
    async fn save(
        &self,
        s: &briefcase_core::models::Snapshot,
    ) -> Result<String, briefcase_core::storage::StorageError> {
        match self {
            Self::Sqlite(b) => b.save(s).await,
            Self::LakeFS(b) => b.save(s).await,
        }
    }
    async fn save_decision(
        &self,
        d: &briefcase_core::models::DecisionSnapshot,
    ) -> Result<String, briefcase_core::storage::StorageError> {
        match self {
            Self::Sqlite(b) => b.save_decision(d).await,
            Self::LakeFS(b) => b.save_decision(d).await,
        }
    }
    async fn load(
        &self,
        id: &str,
    ) -> Result<briefcase_core::models::Snapshot, briefcase_core::storage::StorageError> {
        match self {
            Self::Sqlite(b) => b.load(id).await,
            Self::LakeFS(b) => b.load(id).await,
        }
    }
    async fn load_decision(
        &self,
        id: &str,
    ) -> Result<briefcase_core::models::DecisionSnapshot, briefcase_core::storage::StorageError>
    {
        match self {
            Self::Sqlite(b) => b.load_decision(id).await,
            Self::LakeFS(b) => b.load_decision(id).await,
        }
    }
    async fn query(
        &self,
        q: briefcase_core::storage::SnapshotQuery,
    ) -> Result<Vec<briefcase_core::models::Snapshot>, briefcase_core::storage::StorageError> {
        match self {
            Self::Sqlite(b) => b.query(q).await,
            Self::LakeFS(b) => b.query(q).await,
        }
    }
    async fn delete(&self, id: &str) -> Result<bool, briefcase_core::storage::StorageError> {
        match self {
            Self::Sqlite(b) => b.delete(id).await,
            Self::LakeFS(b) => b.delete(id).await,
        }
    }
    async fn health_check(&self) -> Result<bool, briefcase_core::storage::StorageError> {
        match self {
            Self::Sqlite(b) => b.health_check().await,
            Self::LakeFS(b) => b.health_check().await,
        }
    }
    async fn flush(
        &self,
    ) -> Result<briefcase_core::storage::FlushResult, briefcase_core::storage::StorageError> {
        match self {
            Self::Sqlite(b) => b.flush().await,
            Self::LakeFS(b) => b.flush().await,
        }
    }
}
