use std::num::{NonZeroI32, ParseIntError};

use inpt::{inpt, Inpt};

#[test]
fn test_pair_no_regex() {
    #[derive(Inpt, Debug, PartialEq)]
    struct Pair {
        a: usize,
        b: usize,
    }

    assert_eq!(inpt::<Pair>("42 69").unwrap(), Pair { a: 42, b: 69 });
}

#[test]
fn test_pair_regex() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = r"(\d+) -> (\d+)")]
    struct Pair {
        a: usize,
        b: usize,
    }

    assert_eq!(inpt::<Pair>("42 -> 69").unwrap(), Pair { a: 42, b: 69 });
}

#[test]
fn test_enum() {
    #[derive(Inpt, Debug, PartialEq)]
    enum AOrB {
        #[inpt(regex = r"a(\d+)")]
        A { x: usize },
        #[inpt(regex = r"b(\d+)")]
        B { x: usize },
    }

    assert_eq!(inpt::<AOrB>("a42").unwrap(), AOrB::A { x: 42 });
    assert_eq!(inpt::<AOrB>("b69").unwrap(), AOrB::B { x: 69 });
}

#[test]
fn test_pair_regex_tuple() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = r"(\d+) -> (\d+)")]
    struct Pair(usize, usize);

    assert_eq!(inpt::<Pair>("42 -> 69").unwrap(), Pair(42, 69));
}

#[test]
fn test_enum_tuple() {
    #[derive(Inpt, Debug, PartialEq)]
    enum AOrB {
        #[inpt(regex = r"a(\d+)")]
        A(usize),
        #[inpt(regex = r"b(\d+)")]
        B(usize),
    }

    assert_eq!(inpt::<AOrB>("a42").unwrap(), AOrB::A(42));
    assert_eq!(inpt::<AOrB>("b69").unwrap(), AOrB::B(69));
}

#[test]
fn test_skip() {
    #[derive(Inpt, Debug, PartialEq)]
    struct Pair {
        a: i32,
        #[inpt(skip)]
        b: i32,
        c: i32,
    }

    assert_eq!(inpt::<Pair>("42 69").unwrap(), Pair { a: 42, b: 0, c: 69 });
}

#[test]
fn test_skip_regex() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = r"(\d+) -> (\d+)")]
    struct Pair {
        a: i32,
        #[inpt(skip)]
        b: i32,
        c: i32,
    }

    assert_eq!(
        inpt::<Pair>("42 -> 69").unwrap(),
        Pair { a: 42, b: 0, c: 69 }
    );
}

#[test]
fn test_field_default_trim() {
    #[derive(Inpt, Debug, PartialEq)]
    struct A {
        a: i32,
        b: i32,
    }

    assert_eq!(inpt::<A>(",1,2,").unwrap(), A { a: 1, b: 2 });
}

#[test]
fn test_capture_default_trim() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = "(.*)_(.*)")]
    struct A {
        a: i32,
        b: i32,
    }

    assert_eq!(inpt::<A>(",1,_,2,").unwrap(), A { a: 1, b: 2 });
}

#[test]
fn test_field_iter_default_trim() {
    #[derive(Inpt, Debug, PartialEq)]
    struct A {
        a: i32,
        #[inpt(from_iter = "i32")]
        b: Vec<i32>,
    }

    assert_eq!(
        inpt::<A>(",1,2,3,4,5,").unwrap(),
        A {
            a: 1,
            b: vec![2, 3, 4, 5]
        }
    );
}

#[test]
fn test_capture_iter_default_trim() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = "(.*)_(.*)")]
    struct A {
        a: i32,
        #[inpt(from_iter = "i32")]
        b: Vec<i32>,
    }

    assert_eq!(
        inpt::<A>(",1,_,2,3,4,5,").unwrap(),
        A {
            a: 1,
            b: vec![2, 3, 4, 5]
        }
    );
}

#[test]
fn test_field_iter_custom_trim() {
    #[derive(Inpt, Debug, PartialEq)]
    struct A {
        a: i32,
        #[inpt(from_iter = "i32", trim = "-")]
        b: Vec<i32>,
    }

    assert_eq!(
        inpt::<A>(" 1-2-3-4-5- ").unwrap(),
        A {
            a: 1,
            b: vec![2, 3, 4, 5]
        }
    );
}

#[test]
fn test_capture_iter_custom_trim() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = "(.*)_(.*)")]
    struct A {
        a: i32,
        #[inpt(from_iter = "i32", trim = "-")]
        b: Vec<i32>,
    }

    assert_eq!(
        inpt::<A>(" 1_-2-3-4-5- ").unwrap(),
        A {
            a: 1,
            b: vec![2, 3, 4, 5]
        }
    );
}

#[test]
fn test_field_custom_trim() {
    #[derive(Inpt)]
    struct A {
        _c: char,
        #[inpt(trim = r"\d")]
        x: String,
    }

    assert_eq!(inpt::<A>("_8hello").unwrap().x, "hello");
    assert_eq!(inpt::<A>("_8 hello").unwrap().x, " hello");
}

#[test]
fn test_struct_custom_trim() {
    #[derive(Inpt)]
    #[inpt(trim = r"\d\s")]
    struct A {
        x: String,
    }

    assert_eq!(inpt::<A>("  9 435 34hello03459 2\t").unwrap().x, "hello");
}

#[test]
fn test_capture_custom_trim() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = "_(.+?)_")]
    struct A<'s> {
        #[inpt(before, trim = "_")]
        a: i32,
        #[inpt(trim = r"_")]
        b: &'s str,
        #[inpt(after, trim = "_")]
        c: &'s str,
    }

    assert_eq!(
        inpt::<A>("__42__ohea__rtns").unwrap(),
        A {
            a: 42,
            b: "ohea",
            c: "rtns"
        }
    );
}

#[test]
fn test_struct_and_field_custom_trim() {
    #[derive(Inpt)]
    #[inpt(trim = r"\d\s")]
    struct A {
        x: String,
    }

    #[derive(Inpt)]
    struct B {
        #[inpt(trim = "")]
        x: A,
    }

    #[derive(Inpt)]
    struct C {
        x: A,
    }

    assert_eq!(
        inpt::<B>("  9 435 34hello03459 2\t").unwrap().x.x,
        "9 435 34hello03459 2"
    );
    assert_eq!(inpt::<C>("  9 435 34hello03459 2\t").unwrap().x.x, "hello");
}

#[test]
fn test_optional_gruop() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = r"(\d+) (?:-> (\d+)|<- (\d+))")]
    struct DirPair {
        a: usize,
        b: Option<usize>,
        c: Option<usize>,
    }

    assert_eq!(
        inpt::<DirPair>("42 -> 69").unwrap(),
        DirPair {
            a: 42,
            b: Some(69),
            c: None,
        }
    );
    assert_eq!(
        inpt::<DirPair>("42 <- 69").unwrap(),
        DirPair {
            a: 42,
            b: None,
            c: Some(69),
        }
    );
}

#[test]
fn test_regex_before() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = r"([a-zA-Z]+)")]
    struct Pair<'s> {
        #[inpt(before)]
        rest: f32,
        unit: &'s str,
    }

    assert_eq!(
        inpt::<Pair>("42cm").unwrap(),
        Pair {
            rest: 42.0,
            unit: "cm",
        }
    );
    inpt::<Pair>("42-").unwrap_err();
}

#[test]
fn test_regex_after() {
    #[derive(Inpt, Debug, PartialEq)]
    #[inpt(regex = r"(\+|-)")]
    struct Pair {
        sign: char,
        #[inpt(after)]
        rest: f32,
    }

    assert_eq!(
        inpt::<Pair>("-42").unwrap(),
        Pair {
            sign: '-',
            rest: 42.0,
        }
    );
    assert_eq!(
        inpt::<Pair>("+ 42.0").unwrap(),
        Pair {
            sign: '+',
            rest: 42.0,
        }
    );
    inpt::<Pair>("*42").unwrap_err();
}

#[test]
fn test_split_field() {
    #[derive(Inpt, Debug, PartialEq)]
    struct X {
        #[inpt(split = "Word")]
        a: i32,
        #[inpt(split = "Word")]
        b: i32,
    }

    assert_eq!(inpt::<X>("3.-14.").unwrap(), X { a: 3, b: 14 });
}

#[test]
fn test_split_from_str() {
    #[derive(Inpt, Debug, PartialEq)]
    struct X {
        #[inpt(from_str, split = "Word")]
        a: NonZeroI32,
        #[inpt(from_str, split = "Word")]
        b: NonZeroI32,
    }

    assert_eq!(
        inpt::<X>("3.-14.").unwrap(),
        X {
            a: NonZeroI32::new(3).unwrap(),
            b: NonZeroI32::new(14).unwrap(),
        }
    );
}

#[test]
fn test_split_from_iter() {
    #[derive(Inpt, Debug, PartialEq)]
    struct X {
        #[inpt(from_iter = "char", split = "Line")]
        a: Vec<char>,
        #[inpt(from_iter = "char", split = "Line")]
        b: Vec<char>,
    }

    assert_eq!(
        inpt::<X>("ohea\nrtns").unwrap(),
        X {
            a: vec!['o', 'h', 'e', 'a'],
            b: vec!['r', 't', 'n', 's'],
        }
    );
}

#[test]
fn test_recursion_error() {
    #[derive(Inpt, Debug)]
    #[inpt(bounds = "")]
    struct Blank {
        _next: Box<Blank>,
    }

    let mut error_text = Vec::<u8>::new();
    inpt::<Blank>("_")
        .unwrap_err()
        .annotated(&mut error_text, Some(false))
        .unwrap();
    let error_text = std::str::from_utf8(&error_text).unwrap();
    eprintln!("{error_text}");
    assert!(error_text.contains("<^ derive::test_recursion_error::Blank />"));
}

#[test]
fn test_nested_generics_not_recursion() {
    #[derive(Inpt)]
    struct Wrapper<T> {
        _inner: T,
    }

    inpt::<Wrapper<Wrapper<i32>>>("42").unwrap();
}

#[test]
fn test_derive_with_from() {
    #[derive(Inpt, PartialEq, Debug)]
    #[inpt(from = "i32")]
    struct Foo(i32, i32);

    impl From<i32> for Foo {
        fn from(x: i32) -> Self {
            Foo(0, x)
        }
    }

    assert_eq!(inpt::<Foo>("42").unwrap(), Foo(0, 42));
}

#[test]
fn test_derive_with_try_from_ok() {
    #[derive(Inpt, PartialEq, Debug)]
    #[inpt(try_from = "&'s str")]
    struct Foo(i32, i32);

    impl<'s> TryFrom<&'s str> for Foo {
        type Error = ParseIntError;
        fn try_from(x: &'s str) -> Result<Self, ParseIntError> {
            Ok(Foo(0, x.parse()?))
        }
    }

    assert_eq!(inpt::<Foo>("42").unwrap(), Foo(0, 42));
}

#[test]
fn test_derive_with_try_from_err() {
    #[derive(Inpt, PartialEq, Debug)]
    #[inpt(try_from = "&'s str")]
    struct Foo(i32, i32);

    impl<'s> TryFrom<&'s str> for Foo {
        type Error = ParseIntError;
        fn try_from(x: &'s str) -> Result<Self, ParseIntError> {
            Ok(Foo(0, x.parse()?))
        }
    }

    let mut error_text = Vec::<u8>::new();
    inpt::<Foo>("fourty-two")
        .unwrap_err()
        .annotated(&mut error_text, Some(false))
        .unwrap();
    let error_text = std::str::from_utf8(&error_text).unwrap();
    eprintln!("{error_text}");
    assert!(error_text.contains("Foo><! invalid digit found in string >"));
}

#[test]
fn test_derive_with_from_inherits_trim() {
    #[derive(Inpt, PartialEq, Debug)]
    #[inpt(from = "i32")]
    struct Foo(i32, i32);

    impl From<i32> for Foo {
        fn from(x: i32) -> Self {
            Foo(0, x)
        }
    }

    assert_eq!(inpt::<Foo>(",,42,,").unwrap(), Foo(0, 42));
}

#[test]
fn test_derive_with_from_custom_trim() {
    #[derive(Inpt, PartialEq, Debug)]
    #[inpt(from = "i32", trim = "0")]
    struct Foo(i32, i32);

    impl From<i32> for Foo {
        fn from(x: i32) -> Self {
            Foo(0, x)
        }
    }

    assert_eq!(inpt::<Foo>("004200").unwrap(), Foo(0, 42));
}
