//! Python bindings: the in-memory backend as a ready reference.
//!
//! `MemBackend` exposed as a Python class whose methods return plain dicts
//! (serde→Python via `pythonize`) and raise [`NessieError`] on failure. A
//! zero-dependency ONTAP-shaped backend to script against without a daemon.

use std::str::FromStr;

use pyo3::create_exception;
use pyo3::exceptions::PyException;
use pyo3::prelude::*;
use serde::Serialize;

use nessie_backend_core::{
    BackendError, CloneBackend, SnapshotBackend, SnapshotUuid, VolumeBackend, VolumePatch,
    VolumeSpec, VolumeUuid,
};

use crate::MemBackend;

create_exception!(
    nessie_backend_mem,
    NessieError,
    PyException,
    "A nessie-store backend error."
);

fn err(e: BackendError) -> PyErr {
    NessieError::new_err(e.to_string())
}

fn vol_id(s: &str) -> PyResult<VolumeUuid> {
    VolumeUuid::from_str(s).map_err(|_| NessieError::new_err(format!("invalid volume uuid {s:?}")))
}

fn snap_id(s: &str) -> PyResult<SnapshotUuid> {
    SnapshotUuid::from_str(s)
        .map_err(|_| NessieError::new_err(format!("invalid snapshot uuid {s:?}")))
}

fn to_py<T: Serialize>(py: Python<'_>, value: &T) -> PyResult<Py<PyAny>> {
    pythonize::pythonize(py, value)
        .map(pyo3::Bound::unbind)
        .map_err(|e| NessieError::new_err(format!("serialize to Python failed: {e}")))
}

/// An in-memory ONTAP-shaped storage backend.
#[pyclass(name = "MemBackend")]
pub(crate) struct PyMemBackend {
    inner: MemBackend,
}

#[pymethods]
impl PyMemBackend {
    #[new]
    fn new() -> Self {
        Self {
            inner: MemBackend::new(),
        }
    }

    /// What this backend can do (`{snapshots, clones, replication}`).
    fn capabilities(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        to_py(py, &self.inner.capabilities())
    }

    #[pyo3(signature = (name, size_bytes=None))]
    fn create_volume(
        &self,
        py: Python<'_>,
        name: String,
        size_bytes: Option<u64>,
    ) -> PyResult<Py<PyAny>> {
        let vol = self
            .inner
            .create_volume(VolumeSpec { name, size_bytes })
            .map_err(err)?;
        to_py(py, &vol)
    }

    fn list_volumes(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        to_py(py, &self.inner.list_volumes().map_err(err)?)
    }

    fn get_volume(&self, py: Python<'_>, uuid: &str) -> PyResult<Py<PyAny>> {
        to_py(py, &self.inner.get_volume(&vol_id(uuid)?).map_err(err)?)
    }

    fn delete_volume(&self, uuid: &str) -> PyResult<()> {
        self.inner.delete_volume(&vol_id(uuid)?).map_err(err)
    }

    #[pyo3(signature = (uuid, size_bytes=None, junction_path=None, export_policy=None))]
    fn patch_volume(
        &self,
        py: Python<'_>,
        uuid: &str,
        size_bytes: Option<u64>,
        junction_path: Option<String>,
        export_policy: Option<String>,
    ) -> PyResult<Py<PyAny>> {
        let patch = VolumePatch {
            size_bytes,
            junction_path,
            export_policy,
        };
        let vol = self
            .inner
            .patch_volume(&vol_id(uuid)?, patch)
            .map_err(err)?;
        to_py(py, &vol)
    }

    fn create_snapshot(&self, py: Python<'_>, vol_uuid: &str, name: &str) -> PyResult<Py<PyAny>> {
        let snap = self
            .inner
            .create_snapshot(&vol_id(vol_uuid)?, name)
            .map_err(err)?;
        to_py(py, &snap)
    }

    fn list_snapshots(&self, py: Python<'_>, vol_uuid: &str) -> PyResult<Py<PyAny>> {
        to_py(
            py,
            &self.inner.list_snapshots(&vol_id(vol_uuid)?).map_err(err)?,
        )
    }

    fn get_snapshot(&self, py: Python<'_>, vol_uuid: &str, snap_uuid: &str) -> PyResult<Py<PyAny>> {
        let snap = self
            .inner
            .get_snapshot(&vol_id(vol_uuid)?, &snap_id(snap_uuid)?)
            .map_err(err)?;
        to_py(py, &snap)
    }

    fn delete_snapshot(&self, vol_uuid: &str, snap_uuid: &str) -> PyResult<()> {
        self.inner
            .delete_snapshot(&vol_id(vol_uuid)?, &snap_id(snap_uuid)?)
            .map_err(err)
    }

    fn create_clone(
        &self,
        py: Python<'_>,
        parent_vol_uuid: &str,
        parent_snap_uuid: &str,
        new_name: &str,
    ) -> PyResult<Py<PyAny>> {
        let vol = self
            .inner
            .create_clone(
                &vol_id(parent_vol_uuid)?,
                &snap_id(parent_snap_uuid)?,
                new_name,
            )
            .map_err(err)?;
        to_py(py, &vol)
    }
}

/// The compiled extension module `nessie_backend_mem._core`.
#[pymodule]
fn _core(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add("NessieError", m.py().get_type::<NessieError>())?;
    m.add_class::<PyMemBackend>()?;
    Ok(())
}
