#![cfg(feature = "macros")]

use pyo3::prelude::*;
use pyo3::py_run;
use std::fmt::{Display, Formatter};

mod test_utils;

#[pyclass(eq, str)]
#[derive(Debug, PartialEq)]
pub enum MyEnum2 {
    Variant,
    OtherVariant,
}

impl Display for MyEnum2 {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

#[pyclass(eq, str)]
#[derive(Debug, PartialEq)]
pub enum MyEnum3 {
    #[pyo3(name = "AwesomeVariant")]
    Variant,
    OtherVariant,
}

impl Display for MyEnum3 {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        let variant = match self {
            MyEnum3::Variant => "AwesomeVariant",
            MyEnum3::OtherVariant => "OtherVariant",
        };
        write!(f, "MyEnum.{variant}")
    }
}

#[test]
fn test_enum_class_fmt() {
    Python::attach(|py| {
        let var2 = Py::new(py, MyEnum2::Variant).unwrap();
        let var3 = Py::new(py, MyEnum3::Variant).unwrap();
        let var4 = Py::new(py, MyEnum3::OtherVariant).unwrap();
        py_assert!(py, var2, "str(var2) == 'Variant'");
        py_assert!(py, var3, "str(var3) == 'MyEnum.AwesomeVariant'");
        py_assert!(py, var4, "str(var4) == 'MyEnum.OtherVariant'");
    })
}

#[pyclass(str = "X: {x}, Y: {y}, Z: {z}", skip_from_py_object)]
#[derive(PartialEq, Eq, Clone, PartialOrd)]
pub struct Point {
    x: i32,
    y: i32,
    z: i32,
}

#[test]
fn test_custom_struct_custom_str() {
    Python::attach(|py| {
        let var1 = Py::new(py, Point { x: 1, y: 2, z: 3 }).unwrap();
        py_assert!(py, var1, "str(var1) == 'X: 1, Y: 2, Z: 3'");
    })
}

#[pyclass(str, skip_from_py_object)]
#[derive(PartialEq, Eq, Clone, PartialOrd)]
pub struct Point2 {
    x: i32,
    y: i32,
    z: i32,
}

impl Display for Point2 {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "({}, {}, {})", self.x, self.y, self.z)
    }
}

#[test]
fn test_struct_str() {
    Python::attach(|py| {
        let var1 = Py::new(py, Point2 { x: 1, y: 2, z: 3 }).unwrap();
        py_assert!(py, var1, "str(var1) == '(1, 2, 3)'");
    })
}

#[pyclass(str)]
#[derive(PartialEq, Debug)]
enum ComplexEnumWithStr {
    A(u32),
    B { msg: String },
}

impl Display for ComplexEnumWithStr {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{self:?}")
    }
}

#[test]
fn test_custom_complex_enum_str() {
    Python::attach(|py| {
        let var1 = Py::new(py, ComplexEnumWithStr::A(45)).unwrap();
        let var2 = Py::new(
            py,
            ComplexEnumWithStr::B {
                msg: "Hello".to_string(),
            },
        )
        .unwrap();
        py_assert!(py, var1, "str(var1) == 'A(45)'");
        py_assert!(py, var2, "str(var2) == 'B { msg: \"Hello\" }'");
    })
}

#[pyclass(str = "{0}, {1}, {2}")]
#[derive(PartialEq)]
struct Coord(u32, u32, u32);

#[pyclass(str = "{{{0}, {1}, {2}}}")]
#[derive(PartialEq)]
struct Coord2(u32, u32, u32);

#[test]
fn test_str_representation_by_position() {
    Python::attach(|py| {
        let var1 = Py::new(py, Coord(1, 2, 3)).unwrap();
        let var2 = Py::new(py, Coord2(1, 2, 3)).unwrap();
        py_assert!(py, var1, "str(var1) == '1, 2, 3'");
        py_assert!(py, var2, "str(var2) == '{1, 2, 3}'");
    })
}

#[pyclass(str = "name: {name}: {name}, idn: {idn:03} with message: {msg}")]
#[derive(PartialEq, Debug)]
struct Point4 {
    name: String,
    msg: String,
    idn: u32,
}

#[test]
fn test_mixed_and_repeated_str_formats() {
    Python::attach(|py| {
        let var1 = Py::new(
            py,
            Point4 {
                name: "aaa".to_string(),
                msg: "hello".to_string(),
                idn: 1,
            },
        )
        .unwrap();
        py_run!(
            py,
            var1,
            r#"
        assert str(var1) == 'name: aaa: aaa, idn: 001 with message: hello'
        "#
        );
    })
}

#[pyclass(str = "type: {r#type}")]
struct Foo {
    r#type: u32,
}

#[test]
fn test_raw_identifier_struct_custom_str() {
    Python::attach(|py| {
        let var1 = Py::new(py, Foo { r#type: 3 }).unwrap();
        py_assert!(py, var1, "str(var1) == 'type: 3'");
    })
}
