use pyo3::prelude::*;
use pyo3::types::PyBytes;

use crate::codec::Codec;
use crate::error::KnafehError;

/// A codec that delegates to a Python object with `encode` and `decode` methods.
#[pyclass(name = "Codec")]
pub struct PyCodec {
    _inner: Py<PyAny>,
}

#[pymethods]
impl PyCodec {
    #[new]
    fn new(obj: Py<PyAny>) -> Self {
        Self { _inner: obj }
    }
}

/// Bridge implementation that calls into Python for encode/decode.
#[allow(dead_code)]
pub(crate) struct PythonCodecBridge {
    pub(crate) obj: Py<PyAny>,
}

impl Codec for PythonCodecBridge {
    fn encode(&self, value: &[u8]) -> Result<Vec<u8>, KnafehError> {
        Python::attach(|py| {
            let py_bytes = PyBytes::new(py, value);
            let result = self
                .obj
                .call_method1(py, "encode", (py_bytes,))
                .map_err(|e| KnafehError::Codec(format!("Python encode error: {e}")))?;
            let bytes: &Bound<PyBytes> = result
                .cast_bound(py)
                .map_err(|e| KnafehError::Codec(format!("encode must return bytes: {e}")))?;
            Ok(bytes.as_bytes().to_vec())
        })
    }

    fn decode(&self, data: &[u8]) -> Result<Vec<u8>, KnafehError> {
        Python::attach(|py| {
            let py_bytes = PyBytes::new(py, data);
            let result = self
                .obj
                .call_method1(py, "decode", (py_bytes,))
                .map_err(|e| KnafehError::Codec(format!("Python decode error: {e}")))?;
            let bytes: &Bound<PyBytes> = result
                .cast_bound(py)
                .map_err(|e| KnafehError::Codec(format!("decode must return bytes: {e}")))?;
            Ok(bytes.as_bytes().to_vec())
        })
    }

    fn content_type(&self) -> &str {
        "application/octet-stream"
    }

    fn name(&self) -> &str {
        "python-custom"
    }
}
