use async_tiff::reader::Endianness;
use async_tiff::tags::{
    Compression, ExtraSamples, PhotometricInterpretation, PlanarConfiguration, Predictor,
    ResolutionUnit, SampleFormat,
};
use pyo3::prelude::*;
use pyo3::types::{PyString, PyTuple};
use pyo3::{intern, IntoPyObjectExt};

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub(crate) struct PyCompression(Compression);

impl From<Compression> for PyCompression {
    fn from(value: Compression) -> Self {
        Self(value)
    }
}

impl From<PyCompression> for Compression {
    fn from(value: PyCompression) -> Self {
        value.0
    }
}

impl<'py> FromPyObject<'_, 'py> for PyCompression {
    type Error = PyErr;

    fn extract(obj: Borrowed<'_, 'py, PyAny>) -> Result<Self, Self::Error> {
        Ok(Self(Compression::from_u16_exhaustive(obj.extract()?)))
    }
}

impl<'py> IntoPyObject<'py> for PyCompression {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        to_py_enum_variant(py, intern!(py, "Compression"), self.0.to_u16())
    }
}

pub(crate) struct PyEndianness(Endianness);

impl From<Endianness> for PyEndianness {
    fn from(value: Endianness) -> Self {
        Self(value)
    }
}

impl<'py> IntoPyObject<'py> for PyEndianness {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        let enums_mod = py.import(intern!(py, "async_tiff.enums"))?;
        let endianness_enum = enums_mod.getattr(intern!(py, "Endianness"))?;

        match self.0 {
            Endianness::LittleEndian => endianness_enum.getattr("LittleEndian"),
            Endianness::BigEndian => endianness_enum.getattr("BigEndian"),
        }
    }
}

pub(crate) struct PyExtraSamples(ExtraSamples);

impl From<ExtraSamples> for PyExtraSamples {
    fn from(value: ExtraSamples) -> Self {
        Self(value)
    }
}

impl<'py> IntoPyObject<'py> for PyExtraSamples {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        to_py_enum_variant(py, intern!(py, "ExtraSamples"), self.0.to_u16())
    }
}

pub(crate) struct PyPhotometricInterpretation(PhotometricInterpretation);

impl From<PhotometricInterpretation> for PyPhotometricInterpretation {
    fn from(value: PhotometricInterpretation) -> Self {
        Self(value)
    }
}

impl<'py> IntoPyObject<'py> for PyPhotometricInterpretation {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        to_py_enum_variant(
            py,
            intern!(py, "PhotometricInterpretation"),
            self.0.to_u16(),
        )
    }
}

pub(crate) struct PyPlanarConfiguration(PlanarConfiguration);

impl From<PlanarConfiguration> for PyPlanarConfiguration {
    fn from(value: PlanarConfiguration) -> Self {
        Self(value)
    }
}

impl<'py> IntoPyObject<'py> for PyPlanarConfiguration {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        to_py_enum_variant(py, intern!(py, "PlanarConfiguration"), self.0.to_u16())
    }
}

pub(crate) struct PyResolutionUnit(ResolutionUnit);

impl From<ResolutionUnit> for PyResolutionUnit {
    fn from(value: ResolutionUnit) -> Self {
        Self(value)
    }
}

impl<'py> IntoPyObject<'py> for PyResolutionUnit {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        to_py_enum_variant(py, intern!(py, "ResolutionUnit"), self.0.to_u16())
    }
}

pub(crate) struct PyPredictor(Predictor);

impl From<Predictor> for PyPredictor {
    fn from(value: Predictor) -> Self {
        Self(value)
    }
}

impl<'py> IntoPyObject<'py> for PyPredictor {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        to_py_enum_variant(py, intern!(py, "Predictor"), self.0.to_u16())
    }
}

pub(crate) struct PySampleFormat(SampleFormat);

impl From<SampleFormat> for PySampleFormat {
    fn from(value: SampleFormat) -> Self {
        Self(value)
    }
}

impl<'py> IntoPyObject<'py> for PySampleFormat {
    type Target = PyAny;
    type Output = Bound<'py, PyAny>;
    type Error = PyErr;

    fn into_pyobject(self, py: Python<'py>) -> Result<Self::Output, Self::Error> {
        to_py_enum_variant(py, intern!(py, "SampleFormat"), self.0.to_u16())
    }
}

fn to_py_enum_variant<'py>(
    py: Python<'py>,
    enum_name: &Bound<'py, PyString>,
    value: u16,
) -> PyResult<Bound<'py, PyAny>> {
    let enums_mod = py.import(intern!(py, "async_tiff.enums"))?;
    if let Ok(enum_variant) = enums_mod.call_method1(enum_name, PyTuple::new(py, vec![value])?) {
        Ok(enum_variant)
    } else {
        // If the value is not included in the enum, return the integer itself
        value.into_bound_py_any(py)
    }
}
