use pyo3::{
    prelude::*,
    types::{PyModule, PyTuple},
};

use anyhow::Result;

pub enum PythonScript<'a> {
    File(&'a [u8]),
    Path(&'a str),
}

impl<'a> From<&'a [u8]> for PythonScript<'a> {
    fn from(content: &'a [u8]) -> Self {
        PythonScript::File(content)
    }
}

impl<'a> From<&'a str> for PythonScript<'a> {
    fn from(path: &'a str) -> Self {
        PythonScript::Path(path)
    }
}

pub struct PythonObject {
    script: String,
}

impl PythonObject {
    pub fn new<'a>(script: impl Into<PythonScript<'a>>) -> Result<Self> {
        Ok(Self {
            script: match script.into() {
                PythonScript::File(file) => std::str::from_utf8(file)?.to_string(),
                PythonScript::Path(path) => std::fs::read_to_string(path)?,
            },
        })
    }

    pub fn execute_script<D, F, O, I>(&self, function_name: &str, i: I, f: F) -> Result<O>
    where
        I: ToPyObject,
        D: for<'a> FromPyObject<'a>,
        F: FnOnce(D) -> Result<O>,
    {
        Ok(Python::with_gil(|py| -> Result<O> {
            let module = PyModule::from_code(py, &self.script, "dummy", "dummy")?;

            let args = PyTuple::new(py, [i]);

            Ok(f(module.getattr(function_name)?.call1(args)?.extract()?)?)
        })?)
    }
}

#[cfg(test)]
mod test {
    use anyhow::Result;
    use std::collections::HashMap;

    use super::PythonObject;

    #[test]
    fn verify_python() -> Result<()> {
        let cepi_reader = PythonObject::new("resources/cepi_read_function.py")?;

        let cepi_data = cepi_reader.execute_script(
            "read",
            "resources/dummy.xlsx",
            |r: HashMap<String, HashMap<String, String>>| Ok(r),
        )?;

        println!("{:#?}", cepi_data);

        Ok(())
    }
}
