use core::str::FromStr;
use std::collections::HashMap;
use std::marker::PhantomData;
use std::ops::Range;

use borsh::{BorshDeserialize, BorshSerialize};
use serde::{Deserialize, Serialize};
use serde_with::{serde_as, DisplayFromStr};
use sov_universal_wallet::schema::safe_string::SafeString;
use sov_universal_wallet::schema::{
    ChainData, IndexLinking, Item, Link, Primitive, RollupRoots, Schema, UniversalWallet,
};
use sov_universal_wallet::UniversalWallet;

#[derive(Debug, Serialize)]
struct TestVector {
    /// The JSON object to be serialized to borsh
    input: String,
    /// The resulting borsh encoding as a hex string
    output: String,
    /// The universal wallet schema as a JSON string
    schema: String,
}

// TODO: there's probably a better way than nested macros
macro_rules! encode_decode_tests_simple {
    ($schema:ident, $item:ident, $expected_display:literal) => {
        let borsh_ser = borsh::to_vec(&$item).unwrap();
        let json = serde_json::to_string(&$item).unwrap();

        if cfg!(feature = "test-vectors") {
            use std::time::{SystemTime, UNIX_EPOCH};

            let timestamp_nanos = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_nanos();
            let dir = std::env::var("SOV_UNIVERSAL_WALLET_TEST_VECTORS_DIR")
                .unwrap_or_else(|_| "test-vectors".to_string());
            std::fs::create_dir_all(&dir).unwrap();

            let th = std::thread::current();
            let test_path = th.name().unwrap();
            let test_name = test_path
                .split("::")
                .last()
                .expect("this shouldnt be possible");
            let vector = TestVector {
                input: json.clone(),
                output: hex::encode(&borsh_ser),
                schema: serde_json::to_string(&$schema).unwrap(),
            };

            let file = format!("{dir}/{test_name}-{timestamp_nanos}.json");
            std::fs::write(file, serde_json::to_string_pretty(&vector).unwrap()).unwrap();
        }

        // println!("{}", json);
        assert_eq!($schema.display(0, &borsh_ser).unwrap(), $expected_display);
        assert_eq!($schema.json_to_borsh(0, &json).unwrap(), borsh_ser);
    };
}

macro_rules! encode_decode_tests {
    ($schema_type:ty, $item:ident, $expected_display:literal) => {
        let schema = Schema::of_single_type::<$schema_type>().unwrap();
        // println!("{:?}", &schema);
        encode_decode_tests_simple!(schema, $item, $expected_display);
        let chain_hash = schema.chain_hash().unwrap();
        let schema_json = serde_json::to_string_pretty(&schema).unwrap();
        // println!("{schema_json}");
        let recovered_schema = Schema::from_json(&schema_json).unwrap();
        let recovered_chain_hash = recovered_schema.chain_hash().unwrap();
        assert_eq!(chain_hash, recovered_chain_hash);
        encode_decode_tests_simple!(recovered_schema, $item, $expected_display);
    };
}

pub trait Spec {
    type Address: UniversalWallet;
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithAssociatedType<S: Spec> {
    AssociatedVariant { address: S::Address },
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithWhereClauseAssociatedType<S>
where
    S: Spec,
{
    TheVariant { address: S::Address },
}

#[test]
fn test_associated_types() {
    struct MySpec();
    impl Spec for MySpec {
        type Address = u64;
    }

    let my_enum = EnumWithAssociatedType::<MySpec>::AssociatedVariant { address: 123 };

    encode_decode_tests!(
        EnumWithAssociatedType<MySpec>,
        my_enum,
        "AssociatedVariant { address: 123 }"
    );

    let my_enum = EnumWithWhereClauseAssociatedType::<MySpec>::TheVariant { address: 123 };

    encode_decode_tests!(
        EnumWithWhereClauseAssociatedType<MySpec>,
        my_enum,
        "TheVariant { address: 123 }"
    );
}

#[test]
fn test_inner_item_derive() {
    #[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
    #[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
    struct A {
        my_field: u8,
    }

    let my_a = A { my_field: 32 };
    encode_decode_tests!(A, my_a, "{ my_field: 32 }");
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[borsh(use_discriminant = true)]
pub enum SimpleEnumWithDiscriminants {
    First = 1,
    Second = 6,
    Third,
    Fourth = 100,
    Fifth = 3,
    Sixth,
    Seventh,
    Eighth = 0,
}

#[test]
fn test_simple_enum_with_discriminants() {
    let my_enum = SimpleEnumWithDiscriminants::First;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "First");
    let my_enum = SimpleEnumWithDiscriminants::Second;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "Second");
    let my_enum = SimpleEnumWithDiscriminants::Third;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "Third");
    let my_enum = SimpleEnumWithDiscriminants::Fourth;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "Fourth");
    let my_enum = SimpleEnumWithDiscriminants::Fifth;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "Fifth");
    let my_enum = SimpleEnumWithDiscriminants::Sixth;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "Sixth");
    let my_enum = SimpleEnumWithDiscriminants::Seventh;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "Seventh");
    let my_enum = SimpleEnumWithDiscriminants::Eighth;
    encode_decode_tests!(SimpleEnumWithDiscriminants, my_enum, "Eighth");
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[borsh(use_discriminant = false)]
pub enum EnumWithDiscriminantsDisabledInBorsh {
    First = 1,
    Second = 6,
    Third,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithDiscriminantsDisabledInBorshSurrogateWithoutDiscriminants {
    First,
    Second,
    Third,
}

#[test]
fn test_enum_with_discriminants_disabled_in_borsh() {
    let my_enum = EnumWithDiscriminantsDisabledInBorsh::First;
    encode_decode_tests!(EnumWithDiscriminantsDisabledInBorsh, my_enum, "First");
    let my_enum = EnumWithDiscriminantsDisabledInBorsh::Second;
    encode_decode_tests!(EnumWithDiscriminantsDisabledInBorsh, my_enum, "Second");
    let my_enum = EnumWithDiscriminantsDisabledInBorsh::Third;
    encode_decode_tests!(EnumWithDiscriminantsDisabledInBorsh, my_enum, "Third");

    // Custom test to make sure we serialize enums correctly without a discriminant
    let schema_surrogate = Schema::of_single_type::<
        EnumWithDiscriminantsDisabledInBorshSurrogateWithoutDiscriminants,
    >()
    .unwrap();
    let borsh_from_discriminants = borsh::to_vec(&my_enum).unwrap();
    let json_from_discriminants = serde_json::to_string(&my_enum).unwrap();
    assert_eq!(
        schema_surrogate
            .json_to_borsh(0, &json_from_discriminants)
            .unwrap(),
        borsh_from_discriminants
    );
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone, BorshSerialize, BorshDeserialize)]
/// A type which doesn't derive `UniversalWallet` and doesn't have a schema gen implementation
pub struct NoSchemaU64Wrapper(pub u64);

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[serde(rename_all = "snake_case")]
pub enum TestCall {
    Register(Registration),
    RegisterMany(Vec<RegistrationLike>),
    RegisterSimple(Vec<u8>),
    Withdraw(u64),
    #[serde(skip)]
    #[allow(unused)]
    // We need this variant to test the schema generation of recursive types even though we don't construct it
    Complex(#[cfg_attr(test, sov_wallet(bound = ""))] Box<Complex>),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[serde(rename_all = "snake_case")]
pub enum SimpleEnum {
    One(u8),
    Two(u32),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[serde(rename_all = "snake_case")]
#[sov_wallet(hide_tag)]
pub enum HideTagEnum {
    A(u64),
    B(SimpleEnum),
}

#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct MinimalStruct {
    tokens: u64,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[cfg_attr(
    test,
    sov_wallet(
        show_as = "This is a simple struct, with {} tokens, and the following message: {}. End of template!"
    )
)]
pub struct SimpleStructWithShowAs {
    tokens: u64,
    msg: SafeString,
}

#[test]
fn test_hide_tag_enum() {
    let hide_tag = HideTagEnum::A(0);
    encode_decode_tests!(HideTagEnum, hide_tag, "0");

    let nested_hide_tag = HideTagEnum::B(SimpleEnum::One(4));
    encode_decode_tests!(HideTagEnum, nested_hide_tag, "SimpleEnum.One(4)");
}

#[test]
fn test_simple_struct_schema_with_showas() {
    let my_registration = SimpleStructWithShowAs {
        tokens: 1000,
        msg: "abc".to_string().try_into().unwrap(),
    };

    encode_decode_tests!(SimpleStructWithShowAs, my_registration, "This is a simple struct, with 1000 tokens, and the following message: \"abc\". End of template!");
}

#[derive(
    Debug,
    Default,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub struct SimpleStructWithTemplate {
    #[sov_wallet(template("transfer" = input("amount"), "transfer_2" = value(default)))]
    tokens: u64,
    #[sov_wallet(template("transfer" = value("ababab"), "transfer_2" = input))]
    msg: SafeString,
}

// trivial implementation for tests: comma-separated fields e.g. "4,afdsa"
impl FromStr for SimpleStructWithTemplate {
    type Err = String;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (tokens, msg) = s.split_once(',').unwrap();
        let tokens = tokens.parse().unwrap();
        let msg = msg.try_into().unwrap();
        Ok(Self { tokens, msg })
    }
}

#[derive(
    Debug,
    Default,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub struct SimpleStructWithTemplateAndDisplays {
    #[sov_wallet(template("transfer" = input("tokens"), "receive" = input("tokens")))]
    tokens: u64,
    #[sov_wallet(template("transfer" = input("to_hex"), "receive" = value(bytes("0x0808080808080808080808080808080808080808080808080808080808080808"))))]
    #[sov_wallet(display(hex))]
    hex_address: [u8; 32],
    #[sov_wallet(template("transfer" = input("to_bech32"), "receive" = value(bytes("celestia1pqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyq3k0ptp"))))]
    #[sov_wallet(display(bech32(prefix = "PREFIX_CELESTIA")))]
    bech32_address: [u8; 32],
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub enum SimpleEnumWithTemplate {
    #[sov_wallet(template("transfer_2"))]
    One(SimpleStructWithTemplate),
    #[sov_wallet(template("mint_2"))]
    Two {
        #[sov_wallet(template("mint_2" = input))]
        msg: u8,
    },
    #[sov_wallet(template("mint"))]
    Three(NestedStructWithNonNestedTemplates),
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
#[sov_wallet(template_inherit)]
#[allow(dead_code)]
pub enum SimplerEnumWithTemplate {
    One(SimpleStructWithTemplate),
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub enum SimpleEnumWithTemplateOverrides {
    #[sov_wallet(template_override_ty = "SurrogateSimpleStructWithTemplate")]
    #[sov_wallet(template("mint_a"))]
    One(SimpleStructWithTemplate),
    #[sov_wallet(template_override_ty = "()")]
    Two {
        #[sov_wallet(template("mint_2" = input("mint_msg")))]
        msg: u8,
    },
    Three(NestedStructWithNonNestedTemplates),
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub struct NestedStructWithTemplate {
    inner: SimpleStructWithTemplate,
    #[sov_wallet(template("transfer" = value("6"), "transfer_2" = input("int_msg")))]
    msg: u8,
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub struct NestedStructWithTemplateOverride {
    #[sov_wallet(template_override_ty = "()")]
    inner: SimpleStructWithTemplate,
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub struct SurrogateSimpleStructWithTemplate {
    #[sov_wallet(template("mint_a" = value("19")))]
    tokens: u64,
    #[sov_wallet(template("mint_a" = input("mint_msg")))]
    msg: SafeString,
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub struct NestedStructWithSurrogateTemplateOverride {
    #[sov_wallet(template_override_ty = "SurrogateSimpleStructWithTemplate")]
    inner: SimpleStructWithTemplate,
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    Serialize,
    Deserialize,
    Clone,
    UniversalWallet,
    BorshSerialize,
    BorshDeserialize,
)]
pub struct NestedStructWithNonNestedTemplates {
    #[sov_wallet(template("mint" = value("4,aa")))]
    inner: SimpleStructWithTemplate,
    #[sov_wallet(template("mint" = input("inner")))]
    inner2: SimpleStructWithTemplate,
    #[sov_wallet(template("mint" = input("top_msg")))]
    msg: u32,
}

#[test]
fn test_simple_struct_schema_with_template() {
    let my_registration = SimpleStructWithTemplate {
        tokens: 1000,
        msg: "abc".to_string().try_into().unwrap(),
    };

    encode_decode_tests!(
        SimpleStructWithTemplate,
        my_registration,
        "{ tokens: 1000, msg: \"abc\" }"
    );

    let schema = Schema::of_single_type::<SimpleStructWithTemplate>().unwrap();
    assert_eq!(schema.templates(0).unwrap(), vec!["transfer", "transfer_2"]);

    let transfer_example_encoding = borsh::to_vec(&SimpleStructWithTemplate {
        tokens: 124,
        msg: "ababab".to_string().try_into().unwrap(),
    })
    .unwrap();
    let transfer_template_encoding = schema
        .fill_template_from_json(0, "transfer", "{ \"amount\": 124 }")
        .unwrap();
    assert_eq!(transfer_example_encoding, transfer_template_encoding);

    let transfer_2_example_encoding = borsh::to_vec(&SimpleStructWithTemplate {
        tokens: Default::default(),
        msg: "aaabb".to_string().try_into().unwrap(),
    })
    .unwrap();
    let transfer_2_template_encoding = schema
        .fill_template_from_json(0, "transfer_2", "{ \"msg\": \"aaabb\" }")
        .unwrap();
    assert_eq!(transfer_2_example_encoding, transfer_2_template_encoding);
}

#[test]
fn test_nested_struct_with_template_override() {
    let schema = Schema::of_single_type::<NestedStructWithTemplateOverride>().unwrap();
    assert!(schema.templates(0).unwrap().is_empty());
}

#[test]
fn test_nested_struct_with_surrogate_templates() {
    let my_struct = NestedStructWithSurrogateTemplateOverride {
        inner: SimpleStructWithTemplate {
            tokens: 1000,
            msg: "abc".to_string().try_into().unwrap(),
        },
    };

    encode_decode_tests!(
        NestedStructWithSurrogateTemplateOverride,
        my_struct,
        "{ inner: { tokens: 1000, msg: \"abc\" } }"
    );

    let schema = Schema::of_single_type::<NestedStructWithSurrogateTemplateOverride>().unwrap();
    assert_eq!(schema.templates(0).unwrap(), vec!["mint_a"]);

    let mint_example_encoding = borsh::to_vec(&NestedStructWithSurrogateTemplateOverride {
        inner: SimpleStructWithTemplate {
            tokens: 19,
            msg: "hi".to_string().try_into().unwrap(),
        },
    })
    .unwrap();
    let mint_template_encoding = schema
        .fill_template_from_json(0, "mint_a", "{ \"mint_msg\": \"hi\" }")
        .unwrap();
    assert_eq!(mint_example_encoding, mint_template_encoding);
}

#[test]
fn test_simple_struct_schema_with_template_and_display() {
    let my_registration = SimpleStructWithTemplateAndDisplays {
        tokens: 1000,
        hex_address: [8; 32],
        bech32_address: [9; 32],
    };

    encode_decode_tests!(
        SimpleStructWithTemplateAndDisplays,
        my_registration,
        "{ tokens: 1000, hex_address: 0x0808080808080808080808080808080808080808080808080808080808080808, bech32_address: celestia1pyysjzgfpyysjzgfpyysjzgfpyysjzgfpyysjzgfpyysjzgfpyys5ykmar }"
    );

    let schema = Schema::of_single_type::<SimpleStructWithTemplateAndDisplays>().unwrap();

    let transfer_example_encoding = borsh::to_vec(&SimpleStructWithTemplateAndDisplays {
        tokens: 124,
        hex_address: [9; 32],
        bech32_address: [9; 32],
    })
    .unwrap();
    let transfer_template_encoding = schema
        .fill_template_from_json(0, "transfer", "{ \"tokens\": 124, \"to_hex\": \"0x0909090909090909090909090909090909090909090909090909090909090909\", \"to_bech32\": \"celestia1pyysjzgfpyysjzgfpyysjzgfpyysjzgfpyysjzgfpyysjzgfpyys5ykmar\" }")
        .unwrap();
    assert_eq!(transfer_example_encoding, transfer_template_encoding);

    let receive_example_encoding = borsh::to_vec(&SimpleStructWithTemplateAndDisplays {
        tokens: 125,
        hex_address: [8; 32],
        bech32_address: [8; 32],
    })
    .unwrap();
    let receive_template_encoding = schema
        .fill_template_from_json(0, "receive", "{ \"tokens\": 125 }")
        .unwrap();
    assert_eq!(receive_example_encoding, receive_template_encoding);
}

#[test]
fn test_simple_enum_schema_with_template() {
    let my_registration = SimpleEnumWithTemplate::One(SimpleStructWithTemplate {
        tokens: 1000,
        msg: "abc".to_string().try_into().unwrap(),
    });

    encode_decode_tests!(
        SimpleEnumWithTemplate,
        my_registration,
        "One { tokens: 1000, msg: \"abc\" }"
    );

    let schema = Schema::of_single_type::<SimpleEnumWithTemplate>().unwrap();
    assert_eq!(
        schema.templates(0).unwrap(),
        vec!["mint", "mint_2", "transfer_2"]
    );

    let variant_one_encoding =
        borsh::to_vec(&SimpleEnumWithTemplate::One(SimpleStructWithTemplate {
            tokens: 0,
            msg: "bbb".to_string().try_into().unwrap(),
        }))
        .unwrap();
    let variant_one_template_encoding = schema
        .fill_template_from_json(0, "transfer_2", "{ \"msg\": \"bbb\" }")
        .unwrap();
    assert_eq!(variant_one_encoding, variant_one_template_encoding);

    let variant_two_encoding = borsh::to_vec(&SimpleEnumWithTemplate::Two { msg: 9 }).unwrap();
    let variant_two_template_encoding = schema
        .fill_template_from_json(0, "mint_2", "{ \"msg\": 9 }")
        .unwrap();
    assert_eq!(variant_two_encoding, variant_two_template_encoding);

    let variant_three_encoding = borsh::to_vec(&SimpleEnumWithTemplate::Three(
        NestedStructWithNonNestedTemplates {
            inner: SimpleStructWithTemplate {
                tokens: 4,
                msg: "aa".try_into().unwrap(),
            },
            inner2: SimpleStructWithTemplate {
                msg: "ababa".try_into().unwrap(),
                tokens: 1344,
            },
            msg: 43,
        },
    ))
    .unwrap();
    let variant_three_template_encoding = schema
        .fill_template_from_json(
            0,
            "mint",
            "{ \"top_msg\": 43, \"inner\": { \"msg\": \"ababa\", \"tokens\": 1344 } }",
        )
        .unwrap();
    assert_eq!(variant_three_encoding, variant_three_template_encoding);
}

#[test]
fn test_nested_struct_schema_with_template() {
    let my_registration = NestedStructWithTemplate {
        inner: SimpleStructWithTemplate {
            tokens: 1000,
            msg: "abc".to_string().try_into().unwrap(),
        },
        msg: 19,
    };

    encode_decode_tests!(
        NestedStructWithTemplate,
        my_registration,
        "{ inner: { tokens: 1000, msg: \"abc\" }, msg: 19 }"
    );

    let schema = Schema::of_single_type::<NestedStructWithTemplate>().unwrap();
    assert_eq!(schema.templates(0).unwrap(), vec!["transfer", "transfer_2"]);

    let transfer_example_encoding = borsh::to_vec(&NestedStructWithTemplate {
        inner: SimpleStructWithTemplate {
            tokens: 124,
            msg: "ababab".try_into().unwrap(),
        },
        msg: 6,
    })
    .unwrap();
    let transfer_template_encoding = schema
        .fill_template_from_json(0, "transfer", "{ \"amount\": 124 }")
        .unwrap();
    assert_eq!(transfer_example_encoding, transfer_template_encoding);

    let transfer_2_example_encoding = borsh::to_vec(&NestedStructWithTemplate {
        inner: SimpleStructWithTemplate {
            tokens: 0,
            msg: "two".try_into().unwrap(),
        },
        msg: 93,
    })
    .unwrap();
    let transfer_2_template_encoding = schema
        .fill_template_from_json(0, "transfer_2", "{ \"msg\": \"two\", \"int_msg\": 93 }")
        .unwrap();
    assert_eq!(transfer_2_example_encoding, transfer_2_template_encoding);
}

#[test]
fn test_simple_enum_schema_with_template_overrides() {
    let schema = Schema::of_single_type::<SimpleEnumWithTemplateOverrides>().unwrap();
    assert_eq!(schema.templates(0).unwrap(), vec!["mint_a"]);
}

#[test]
fn test_nested_struct_schema_with_non_nested_template() {
    let my_registration = NestedStructWithNonNestedTemplates {
        inner: SimpleStructWithTemplate {
            tokens: 1000,
            msg: "abc".to_string().try_into().unwrap(),
        },
        inner2: SimpleStructWithTemplate {
            tokens: 1000,
            msg: "abc".to_string().try_into().unwrap(),
        },
        msg: 40203,
    };
    encode_decode_tests!(NestedStructWithNonNestedTemplates, my_registration, "{ inner: { tokens: 1000, msg: \"abc\" }, inner2: { tokens: 1000, msg: \"abc\" }, msg: 40203 }");

    let schema = Schema::of_single_type::<NestedStructWithNonNestedTemplates>().unwrap();
    assert_eq!(schema.templates(0).unwrap(), vec!["mint"]);
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum SimpleEnumWithShowAs {
    #[cfg_attr(
        test,
        sov_wallet(show_as = "This variant has {} tokens, and the following message: {}. End.")
    )]
    One { tokens: u64, msg: SafeString },
    #[cfg_attr(
        test,
        sov_wallet(show_as = "This variant is a tuple with two fields: a string {} an u8 {}.")
    )]
    Two(SafeString, u8),
}

#[test]
fn test_simple_enum_schema_with_showas() {
    let var_one = SimpleEnumWithShowAs::One {
        tokens: 1000,
        msg: "abc".to_string().try_into().unwrap(),
    };
    encode_decode_tests!(
        SimpleEnumWithShowAs,
        var_one,
        "This variant has 1000 tokens, and the following message: \"abc\". End."
    );

    let var_two = SimpleEnumWithShowAs::Two("def".to_string().try_into().unwrap(), 19);
    encode_decode_tests!(
        SimpleEnumWithShowAs,
        var_two,
        "This variant is a tuple with two fields: a string \"def\" an u8 19."
    );
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[serde(rename_all = "snake_case")]
pub struct Registration {
    address: [u8; 32],
    role: Role,
    tokens: u64,
    #[cfg_attr(test, sov_wallet(as_ty = "u64"))]
    schemaless: NoSchemaU64Wrapper,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct StringWrapper(pub SafeString);

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(BorshSerialize, BorshDeserialize))]
pub struct SchemalessStringWrapper(pub SafeString);

#[cfg(test)]
impl UniversalWallet for SchemalessStringWrapper {
    fn scaffold() -> Item<IndexLinking> {
        Item::Atom(Primitive::String)
    }
    fn get_child_links(_schema: &mut Schema) -> Vec<Link> {
        Vec::new()
    }
}

#[derive(Debug, PartialEq, Eq, Clone, UniversalWallet)]
#[cfg_attr(test, derive(BorshSerialize, BorshDeserialize))]
#[allow(dead_code)]
pub struct VecOfThing {
    memo: Vec<StringWrapper>,
}

#[derive(Debug, PartialEq, Eq, Clone, UniversalWallet)]
#[cfg_attr(test, derive(BorshSerialize, BorshDeserialize))]
#[allow(dead_code)]
pub struct VecOfWrapper {
    memo: Vec<SchemalessStringWrapper>,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct RegistrationLike {
    address: [u8; 32],
    some_bytes: Vec<u8>,
    extra_complexity: Vec<AThirdComplexType>,
}

type VecAlias = Vec<u8>;
type IntAlias = i32;
type TestCallAlias = TestCall;

#[derive(Debug, PartialEq, Serialize, Eq, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
#[serde(rename_all = "snake_case")]
pub enum TestCallRec<T> {
    Withdraw(Box<T>),
    // #[serde(skip)]
    #[allow(unused)]
    // We need this variant to test the schema generation of recursive types even though we don't construct it
    Complex(#[sov_wallet(bound = "Box<T>: UniversalWallet")] Box<ComplexRec<T>>),
    // Complex(Box<ComplexRec<T>>),
}

#[derive(Debug, PartialEq, Serialize, Eq, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
#[serde(rename_all = "snake_case")]
pub enum TestCallStructRec<T> {
    Withdraw(Box<T>),
    #[allow(unused)]
    Complex {
        #[sov_wallet(bound = "Box<T>: UniversalWallet")]
        rec_field: Box<ComplexRec<T>>,
    },
}

#[derive(Debug, PartialEq, Serialize, Eq, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
pub struct ComplexRec<T> {
    unnested_enum: TestCallRec<T>,
}

#[derive(Debug, PartialEq, Serialize, Eq, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
pub struct VariousTypes {
    option: Option<MinimalStruct>,
    other_option: Option<u8>,
    range: Range<u8>,
    hash_map: HashMap<SafeString, u8>,
    enum_map: HashMap<Role, u8>,
    num_map: HashMap<u32, u8>,
}

#[test]
fn test_various_types() {
    let mut hash_map: HashMap<SafeString, u8> = HashMap::new();
    hash_map.insert("a".try_into().unwrap(), 9);
    hash_map.insert("b".try_into().unwrap(), 16);
    let mut num_map: HashMap<u32, u8> = HashMap::new();
    num_map.insert(10_000, 12);
    num_map.insert(20_000, 13);
    let mut enum_map: HashMap<Role, u8> = HashMap::new();
    enum_map.insert(Role::Attester, 9);
    enum_map.insert(Role::Challenger, 16);
    let my_types = VariousTypes {
        option: Some(MinimalStruct { tokens: 3 }),
        other_option: None,
        range: 4..8,
        hash_map,
        enum_map,
        num_map,
    };

    encode_decode_tests!(
        VariousTypes,
        my_types,
        "{ option: { tokens: 3 }, other_option: None, range: 4..8, hash_map: { \"a\": 9, \"b\": 16 }, enum_map: { .Attester: 9, .Challenger: 16 }, num_map: { 10000: 12, 20000: 13 } }"
    );
}

#[test]
fn test_tuple_schema_recursive_generic() {
    let my_call = TestCallRec::<u64>::Withdraw(Box::new(43));

    encode_decode_tests!(TestCallRec<u64>, my_call, "Withdraw(43)");
}

#[test]
fn test_tuple_struct_schema_recursive_generic() {
    let my_call = TestCallStructRec::<u64>::Withdraw(Box::new(43));

    encode_decode_tests!(TestCallStructRec<u64>, my_call, "Withdraw(43)");
}

#[serde_as]
#[derive(Debug, PartialEq, Serialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
pub struct StructWithPrimitives {
    pub u8: u8,
    #[serde_as(as = "DisplayFromStr")]
    pub u8_str: u8,
    pub u16: u16,
    #[serde_as(as = "DisplayFromStr")]
    pub u16_str: u16,
    pub u32: u32,
    #[serde_as(as = "DisplayFromStr")]
    pub u32_str: u32,
    pub u64: u64,
    #[serde_as(as = "DisplayFromStr")]
    pub u64_str: u64,
    // No native u128 because it doesn't fit in JSON numbers
    #[serde_as(as = "DisplayFromStr")]
    pub u128: u128,
    pub i8: i8,
    #[serde_as(as = "DisplayFromStr")]
    pub i8_str: i8,
    pub i16: i16,
    #[serde_as(as = "DisplayFromStr")]
    pub i16_str: i16,
    pub i32: i32,
    #[serde_as(as = "DisplayFromStr")]
    pub i32_str: i32,
    pub i64: i64,
    #[serde_as(as = "DisplayFromStr")]
    pub i64_str: i64,
    #[serde_as(as = "DisplayFromStr")]
    pub i128: i128,
    pub bool: bool,
    #[serde_as(as = "DisplayFromStr")]
    pub bool_str: bool,
    pub f32: f32,
    #[serde_as(as = "DisplayFromStr")]
    pub f32_str: f32,
    pub f64: f64,
    #[serde_as(as = "DisplayFromStr")]
    pub f64_str: f64,
    pub string: SafeString,
}

#[test]
fn test_struct_with_primitives() {
    let my_struct = StructWithPrimitives {
        u8: 92,
        u8_str: 82,
        u16: 392,
        u16_str: 492,
        u32: 15_472_432,
        u32_str: 25_472_432,
        u64: 340_542_814_143,
        u64_str: 240_542_814_143,
        u128: 180_446_744_073_709_551_615,
        i8: -92,
        i8_str: -82,
        i16: -392,
        i16_str: -492,
        i32: -15_472_432,
        i32_str: -25_472_432,
        i64: -340_542_814_143,
        i64_str: -240_542_814_143,
        i128: -180_446_744_073_709_551_615,
        bool: true,
        bool_str: false,
        f32: 45.59,
        f32_str: 35.59,
        f64: 9716235.31632546,
        f64_str: 8716235.31632546,
        string: "Hello".to_string().try_into().unwrap(),
    };

    encode_decode_tests!(StructWithPrimitives, my_struct, "{ u8: 92, u8_str: 82, u16: 392, u16_str: 492, u32: 15472432, u32_str: 25472432, u64: 340542814143, u64_str: 240542814143, u128: 180446744073709551615, i8: -92, i8_str: -82, i16: -392, i16_str: -492, i32: -15472432, i32_str: -25472432, i64: -340542814143, i64_str: -240542814143, i128: -180446744073709551615, bool: true, bool_str: false, f32: 45.59, f32_str: 35.59, f64: 9716235.31632546, f64_str: 8716235.31632546, string: \"Hello\" }");
}

#[derive(Debug, PartialEq, Serialize, Eq, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
pub enum TestCallRecNonGeneric {
    Withdraw(u64),
    // #[serde(skip)]
    #[allow(unused)]
    // We need this variant to test the schema generation of recursive types even though we don't construct it
    Complex(#[sov_wallet(bound = "")] Box<ComplexRecNonGeneric>),
}
#[derive(Debug, PartialEq, Serialize, Eq, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
pub struct ComplexRecNonGeneric {
    unnested_enum: TestCallRecNonGeneric,
}
#[test]
fn test_medium_struct_schema_recursive_nongeneric() {
    let my_call = TestCallRecNonGeneric::Withdraw(43);

    encode_decode_tests!(TestCallRecNonGeneric, my_call, "Withdraw(43)");
}

const PREFIX_SOV: &str = "sov";
const PREFIX_CELESTIA: &str = "celestia";

#[derive(Debug, PartialEq, Serialize, Eq, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct Complex {
    #[cfg_attr(test, sov_wallet(display(bech32(prefix = "PREFIX_SOV"))))]
    rollup_address: [u8; 32],
    #[cfg_attr(
        test,
        sov_wallet(display(bech32m(prefix = "PREFIX_CELESTIA"))),
        serde(with = "serde_arrays")
    )]
    da_address: [u8; 64],
    #[cfg_attr(test, sov_wallet(display(decimal)))]
    message: Vec<u8>,
    // Important: we cover the case of...
    // - an aliased field, followed by a field with a different schema, followed by a Vec containing the aliased type
    // using SchemalessStringWrapper. Be careful about reording and/or deleting the aliased field here, since
    // we can have regressions on that edge case.
    first_item: SchemalessStringWrapper,
    role: Role,
    tokens: u64,
    memo: Vec<SchemalessStringWrapper>,
    registration: Registration,
    events: Vec<Vec<u8>>,
    child_addresses: Vec<[u8; 32]>,
    nested_vec_bytes: Vec<Vec<Vec<u8>>>,
    nested_vec_enum: Vec<Vec<TestCall>>,
    aliased_vec: VecAlias,
    aliased_int: IntAlias,
    aliased_call: TestCallAlias,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct Generic<T> {
    contents: T,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct NestedGeneric<T> {
    contents: Generic<T>,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct StructWithIntegerDisplays {
    #[cfg_attr(test, sov_wallet(fixed_point(2)))]
    direct_fp: u64,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(0))))]
    from_field_before: i64,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(4, offset = 4))))]
    from_field_after: u64,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(3))))]
    from_field_self: u64,
    #[cfg_attr(test, sov_wallet(hidden))]
    array_field: [u8; 5],
}

#[test]
fn test_struct_with_integer_fixedpoints() {
    let my_struct = StructWithIntegerDisplays {
        direct_fp: 4,
        from_field_before: -21,
        from_field_after: 4000,
        from_field_self: 3,
        array_field: [3, 3, 3, 3, 2],
    };

    encode_decode_tests!(StructWithIntegerDisplays, my_struct,
        "{ direct_fp: 0.04, from_field_before: -0.0021, from_field_after: 40, from_field_self: 0.003 }"
    );
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct TupleWithIntegerDisplaysAndNesting(
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(5, offset = 1))))] u16,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(0))))] i8,
    StructWithIntegerDisplays,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(4))))] u128,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(4))))] u8,
    #[cfg_attr(test, sov_wallet(hidden))] [u8; 2],
);

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct StructWithIntegerDisplaysAndNesting {
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(0))))]
    from_field_self: u64,
    nested_tuple: TupleWithIntegerDisplaysAndNesting,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(3))))]
    from_field_after: u128,
    #[cfg_attr(test, sov_wallet(fixed_point(from_field(0))))]
    from_field_before: u64,
}

#[test]
fn test_struct_with_integer_fixedpoints_and_nesting() {
    let my_struct = StructWithIntegerDisplaysAndNesting {
        from_field_self: 2,
        nested_tuple: TupleWithIntegerDisplaysAndNesting(
            2,
            -21,
            StructWithIntegerDisplays {
                direct_fp: 4,
                from_field_before: -21,
                from_field_after: 4000,
                from_field_self: 3,
                array_field: [3, 3, 3, 3, 4],
            },
            475000,
            4,
            [19, 2],
        ),
        from_field_after: 40_000_000_000_000_000, // 40
        from_field_before: 15,
    };

    encode_decode_tests!(StructWithIntegerDisplaysAndNesting, my_struct,
        "{ from_field_self: 0.02, nested_tuple: (0.02, -0.21, { direct_fp: 0.04, from_field_before: -0.0021, from_field_after: 0.4, from_field_self: 0.003 }, 47.5, 0.0004), from_field_after: 40, from_field_before: 0.15 }"
    );
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct StructWithBase58 {
    #[cfg_attr(test, sov_wallet(display(base58)))]
    address: [u8; 32],
    #[cfg_attr(test, sov_wallet(display(base58)))]
    extra_bytes: Vec<u8>,
    role: Role,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub struct AThirdComplexType {
    #[cfg_attr(test, sov_wallet(display(bech32(prefix = "PREFIX_CELESTIA"))))]
    address: [u8; 32],
    #[cfg_attr(test, sov_wallet(display(decimal)))]
    extra_bytes: Vec<u8>,
    role: Role,
}

#[derive(
    Debug,
    PartialEq,
    Eq,
    PartialOrd,
    Ord,
    Hash,
    Clone,
    Serialize,
    Deserialize,
    BorshDeserialize,
    BorshSerialize,
    UniversalWallet,
)]
pub enum Role {
    Attester,
    Challenger,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[serde(rename_all = "snake_case", rename = "a")]
pub enum RuntimeCall {
    TestCall(TestCall),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithStruct {
    Foo {
        first_field: u64,
        second_field: SafeString,
        third_field: u32,
    },
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
#[allow(dead_code)]
pub enum EnumWithTwoStructs {
    Foo {
        first_field: u64,
        second_field: SafeString,
        third_field: u32,
    },
    Bar {
        first_field: u64,
        second_field: u8,
        third_field: u32,
    },
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithStructAndGeneric<T> {
    Foo {
        first_field: u64,
        second_field: Generic<T>,
        third_field: u32,
    },
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithStructAndThreeGenerics<T, U, V> {
    VarA {
        first_field: u64,
        second_field: Generic<T>,
        third_field: u32,
    },
    VarB {
        first_field: u64,
        second_field: Generic<U>,
        third_field: u32,
    },
    VarC {
        first_field: u64,
        second_field: Generic<Generic<V>>,
        third_field: u32,
    },
    VarD,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithMultiTupleSimple {
    TheVariant(u64, EnumWithStruct, SafeString),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithMultiTuple {
    TheVariant(u64, EnumWithStruct, RuntimeCall),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithMultiTupleAndGenerics<T, U, V> {
    One(u64, EnumWithStructAndThreeGenerics<T, U, V>, SafeString),
    Two(Generic<U>),
    Three,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize, BorshDeserialize))]
pub enum EnumWithIdenticalTuples {
    FirstVariant(u64, EnumWithStruct, SafeString),
    SecondVariant(u64, EnumWithStruct, SafeString),
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
pub struct WithSilentField<T> {
    int: u64,
    #[cfg_attr(test, sov_wallet(hidden))]
    skipped: T,
    str: SafeString,
    phantom: PhantomData<u64>,
}

#[derive(Debug, PartialEq, Eq, Serialize, Deserialize, Clone)]
#[cfg_attr(test, derive(UniversalWallet, BorshSerialize))]
struct WithTuples {
    #[allow(unused_parens)]
    double: (u64, u64),
    mixed: (u64, SafeString, Role),
    quintuple: (u64, u64, u64, u64, u64),
    octuple: (u64, u64, u64, u64, u64, u64, u64, u64),
}

#[test]
fn test_tuples() {
    let my_with_tuples = WithTuples {
        double: (5, 8),
        mixed: (
            13214,
            "hello".to_string().try_into().unwrap(),
            Role::Challenger,
        ),
        quintuple: (1, 2, 3, 4, 5),
        octuple: (1, 2, 3, 4, 5, 6, 7, 8),
    };

    encode_decode_tests!(WithTuples, my_with_tuples, "{ double: (5, 8), mixed: (13214, \"hello\", RoleChallenger), quintuple: (1, 2, 3, 4, 5), octuple: (1, 2, 3, 4, 5, 6, 7, 8) }");
}

#[test]
fn test_enum_with_complex_tuples() {
    let runtime_call = RuntimeCall::TestCall(TestCall::Register(Registration {
        address: [17; 32],
        role: Role::Attester,
        tokens: 1000,
        schemaless: NoSchemaU64Wrapper(123),
    }));

    let my_enum = EnumWithMultiTuple::TheVariant(
        16,
        EnumWithStruct::Foo {
            first_field: 84,
            second_field: "abcd".to_string().try_into().unwrap(),
            third_field: 14,
        },
        runtime_call,
    );

    encode_decode_tests!(EnumWithMultiTuple, my_enum,
        "TheVariant(16, Foo { first_field: 84, second_field: \"abcd\", third_field: 14 }, TestCall.Register { address: 0x1111111111111111111111111111111111111111111111111111111111111111, role: Attester, tokens: 1000, schemaless: 123 })"
    );
}

#[test]
fn test_enum_with_simple_tuples() {
    let my_enum = EnumWithMultiTupleSimple::TheVariant(
        16,
        EnumWithStruct::Foo {
            first_field: 84,
            second_field: "abcd".to_string().try_into().unwrap(),
            third_field: 14,
        },
        "hello".to_string().try_into().unwrap(),
    );

    encode_decode_tests!(EnumWithMultiTupleSimple, my_enum,
        "TheVariant(16, Foo { first_field: 84, second_field: \"abcd\", third_field: 14 }, \"hello\")"
    );
}

#[test]
fn test_enum_with_identical_tuples() {
    let my_enum = EnumWithIdenticalTuples::SecondVariant(
        16,
        EnumWithStruct::Foo {
            first_field: 84,
            second_field: "abcd".to_string().try_into().unwrap(),
            third_field: 14,
        },
        "hello".to_string().try_into().unwrap(),
    );

    encode_decode_tests!(EnumWithIdenticalTuples, my_enum,
        "SecondVariant(16, Foo { first_field: 84, second_field: \"abcd\", third_field: 14 }, \"hello\")"
    );
}

#[test]
fn test_enum_with_struct() {
    let my_with_tuples = EnumWithStruct::Foo {
        first_field: 84,
        second_field: "abcd".to_string().try_into().unwrap(),
        third_field: 14,
    };

    encode_decode_tests!(
        EnumWithStruct,
        my_with_tuples,
        "Foo { first_field: 84, second_field: \"abcd\", third_field: 14 }"
    );
}

#[test]
fn test_enum_with_struct_and_generic() {
    let my_with_tuples = EnumWithStructAndGeneric::Foo {
        first_field: 84,
        second_field: Generic { contents: 52 },
        third_field: 14,
    };

    encode_decode_tests!(
        EnumWithStructAndGeneric<u32>,
        my_with_tuples,
        "Foo { first_field: 84, second_field: { contents: 52 }, third_field: 14 }"
    );
}

#[test]
fn test_enum_with_struct_and_multiple_generics() {
    let my_with_generics: EnumWithStructAndThreeGenerics<u32, u8, i8> =
        EnumWithStructAndThreeGenerics::VarA {
            first_field: 84,
            second_field: Generic {
                contents: 2_000_000_000,
            },
            third_field: 14,
        };

    encode_decode_tests!(
        EnumWithStructAndThreeGenerics<u32, u8, i8>,
        my_with_generics,
        "VarA { first_field: 84, second_field: { contents: 2000000000 }, third_field: 14 }"
    );
}

#[test]
fn test_enum_with_tuples_and_generics() {
    let my_var_two: EnumWithMultiTupleAndGenerics<u32, u8, i8> =
        EnumWithMultiTupleAndGenerics::Two(Generic { contents: 19 });
    let my_var_one: EnumWithMultiTupleAndGenerics<u32, u8, i8> = EnumWithMultiTupleAndGenerics::One(
        1245345,
        EnumWithStructAndThreeGenerics::VarC {
            first_field: 435653,
            second_field: Generic {
                contents: Generic { contents: -5 },
            },
            third_field: 73242,
        },
        "abdsf".to_string().try_into().unwrap(),
    );

    encode_decode_tests!(EnumWithMultiTupleAndGenerics<u32, u8, i8>, my_var_two, "Two { contents: 19 }");

    encode_decode_tests!(EnumWithMultiTupleAndGenerics<u32, u8, i8>, my_var_one,
    "One(1245345, VarC { first_field: 435653, second_field: { contents: { contents: -5 } }, third_field: 73242 }, \"abdsf\")"
    );
}

#[test]
fn test_minimal_enum_schema() {
    let item = Role::Attester;
    encode_decode_tests!(Role, item, "Attester");
}

#[test]
fn test_minimal_struct_schema() {
    let my_registration = MinimalStruct { tokens: 1000 };

    encode_decode_tests!(MinimalStruct, my_registration, "{ tokens: 1000 }");
}

#[test]
fn test_simple_struct_schema() {
    let my_registration = Registration {
        address: [17; 32],
        role: Role::Attester,
        tokens: 1000,
        schemaless: NoSchemaU64Wrapper(123),
    };

    encode_decode_tests!(Registration, my_registration, "{ address: 0x1111111111111111111111111111111111111111111111111111111111111111, role: Attester, tokens: 1000, schemaless: 123 }");
}

#[test]
fn test_multiobject_schema() {
    let schema =
        Schema::of_rollup_types_with_chain_data::<Role, MinimalStruct, Registration, SimpleEnum>(
            ChainData {
                chain_id: 4321,
                chain_name: "Testchain".to_string(),
            },
        )
        .unwrap();

    let my_role = Role::Attester;
    let my_minimal_struct = MinimalStruct { tokens: 1000 };
    let my_registration = Registration {
        address: [17; 32],
        role: Role::Attester,
        tokens: 1000,
        schemaless: NoSchemaU64Wrapper(123),
    };

    let orig_hash = schema.chain_hash().unwrap();
    let schema_json = serde_json::to_string_pretty(&schema).unwrap();
    let schema = Schema::from_json(&schema_json).unwrap();
    let hash = schema.chain_hash().unwrap();
    assert_eq!(orig_hash, hash);

    // TODO: ugly
    let role_borsh_ser = borsh::to_vec(&my_role).unwrap();
    let role_json = serde_json::to_string(&my_role).unwrap();
    assert_eq!(
        schema
            .display(
                schema
                    .rollup_expected_index(RollupRoots::Transaction)
                    .unwrap(),
                &role_borsh_ser
            )
            .unwrap(),
        "Attester"
    );
    assert_eq!(
        schema
            .json_to_borsh(
                schema
                    .rollup_expected_index(RollupRoots::Transaction)
                    .unwrap(),
                &role_json
            )
            .unwrap(),
        role_borsh_ser
    );
    let struct_borsh_ser = borsh::to_vec(&my_minimal_struct).unwrap();
    let struct_json = serde_json::to_string(&my_minimal_struct).unwrap();
    assert_eq!(
        schema
            .display(
                schema
                    .rollup_expected_index(RollupRoots::UnsignedTransaction)
                    .unwrap(),
                &struct_borsh_ser
            )
            .unwrap(),
        "{ tokens: 1000 }"
    );
    assert_eq!(
        schema
            .json_to_borsh(
                schema
                    .rollup_expected_index(RollupRoots::UnsignedTransaction)
                    .unwrap(),
                &struct_json
            )
            .unwrap(),
        struct_borsh_ser
    );
    let reg_borsh_ser = borsh::to_vec(&my_registration).unwrap();
    let reg_json = serde_json::to_string(&my_registration).unwrap();
    assert_eq!(schema.display(schema.rollup_expected_index(RollupRoots::RuntimeCall).unwrap(), &reg_borsh_ser).unwrap(), "{ address: 0x1111111111111111111111111111111111111111111111111111111111111111, role: Attester, tokens: 1000, schemaless: 123 }");
    assert_eq!(
        schema
            .json_to_borsh(
                schema
                    .rollup_expected_index(RollupRoots::RuntimeCall)
                    .unwrap(),
                &reg_json
            )
            .unwrap(),
        reg_borsh_ser
    );
}

#[test]
fn test_medium_struct_schema() {
    let my_call = RuntimeCall::TestCall(TestCall::Register(Registration {
        address: [17; 32],
        role: Role::Attester,
        tokens: 1000,
        schemaless: NoSchemaU64Wrapper(123),
    }));

    encode_decode_tests!(RuntimeCall, my_call, "TestCall.Register { address: 0x1111111111111111111111111111111111111111111111111111111111111111, role: Attester, tokens: 1000, schemaless: 123 }");
}

#[test]
fn test_vec_schema() {
    let my_call = RuntimeCall::TestCall(TestCall::RegisterMany(vec![RegistrationLike {
        address: [23; 32],
        some_bytes: vec![1, 2, 3, 4, 5],
        extra_complexity: vec![AThirdComplexType {
            address: [17; 32],
            extra_bytes: vec![6, 7, 8, 9, 10],
            role: Role::Attester,
        }],
    }]));

    encode_decode_tests!(RuntimeCall, my_call,
        "TestCall.RegisterMany [{ address: 0x1717171717171717171717171717171717171717171717171717171717171717, some_bytes: 0x0102030405, extra_complexity: [{ address: celestia1zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zyg3zygsr0ealj, extra_bytes: [6, 7, 8, 9, 10], role: Attester }] }]"
    );
}

#[test]
fn test_base58() {
    let my_call = StructWithBase58 {
        address: [17; 32],
        extra_bytes: vec![6, 7, 8, 9, 10],
        role: Role::Attester,
    };

    encode_decode_tests!(StructWithBase58, my_call,
        "{ address: 29d2S7vB453rNYFdR5Ycwt7y9haRT5fwVwL9zTmBhfV2, extra_bytes: gScbNR, role: Attester }"
    );
}

#[test]
fn test_vec_simple_schema() {
    let my_call = RuntimeCall::TestCall(TestCall::RegisterSimple(vec![1, 2, 3]));

    encode_decode_tests!(RuntimeCall, my_call, "TestCall.RegisterSimple(0x010203)");
}

#[test]
fn test_vec_string() {
    let my_call = vec![
        StringWrapper("hello".to_string().try_into().unwrap()),
        StringWrapper("world".to_string().try_into().unwrap()),
    ];

    encode_decode_tests!(Vec<StringWrapper>, my_call, r#"["hello", "world"]"#);
}

#[test]
fn test_complex_type() {
    let my_call = Complex {
        rollup_address: [1; 32],
        da_address: [2; 64],
        message: vec![3, 2, 1],
        first_item: SchemalessStringWrapper("hello".to_string().try_into().unwrap()),
        role: Role::Attester,
        tokens: 1000,
        memo: vec![SchemalessStringWrapper(
            "This is a memo".to_string().try_into().unwrap(),
        )],
        registration: Registration {
            address: [17; 32],
            role: Role::Attester,
            tokens: 1000,
            schemaless: NoSchemaU64Wrapper(123),
        },
        events: vec![b"hello".to_vec(), b"goodbye".to_vec()],
        child_addresses: vec![[3; 32], [4; 32]],
        nested_vec_bytes: vec![vec![vec![0, 1, 2]]],
        nested_vec_enum: vec![vec![TestCall::Withdraw(1)], vec![TestCall::Withdraw(2)]],
        aliased_vec: vec![7, 8, 9],
        aliased_int: 1234567,
        aliased_call: TestCall::Register(Registration {
            address: [17; 32],
            role: Role::Attester,
            tokens: 1000,
            schemaless: NoSchemaU64Wrapper(123),
        }),
    };

    encode_decode_tests!(Complex, my_call,
        "{ rollup_address: sov1qyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqslg48nn, da_address: celestia1qgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqszqgpqyqs08sssm, message: [3, 2, 1], first_item: \"hello\", role: Attester, tokens: 1000, memo: [\"This is a memo\"], registration: { address: 0x1111111111111111111111111111111111111111111111111111111111111111, role: Attester, tokens: 1000, schemaless: 123 }, events: [0x68656c6c6f, 0x676f6f64627965], child_addresses: [0x0303030303030303030303030303030303030303030303030303030303030303, 0x0404040404040404040404040404040404040404040404040404040404040404], nested_vec_bytes: [[0x000102]], nested_vec_enum: [[TestCall.Withdraw(1)], [TestCall.Withdraw(2)]], aliased_vec: 0x070809, aliased_int: 1234567, aliased_call: Register { address: 0x1111111111111111111111111111111111111111111111111111111111111111, role: Attester, tokens: 1000, schemaless: 123 } }"
    );
}

#[test]
fn test_silent_simple_field() {
    let my_call: WithSilentField<SafeString> = WithSilentField {
        int: 123,
        skipped: "this should be skipped".try_into().unwrap(),
        str: "this should be included".try_into().unwrap(),
        phantom: Default::default(),
    };

    encode_decode_tests!(
        WithSilentField<SafeString>,
        my_call,
        "{ int: 123, str: \"this should be included\" }"
    );
}
// TODO: Are Vec<Box<T>> Primitive when T: Primitive? I think so.
// What about Vec<Box<u8>>? Is that a bytevec? I think so - but how do we resolve it?

#[test]
fn test_primtive_indirection() {
    let generic: Generic<Box<Vec<u8>>> = Generic {
        contents: Box::new(vec![12, 34]),
    };

    encode_decode_tests!(Generic<Box<Vec<u8>>>, generic, "{ contents: 0x0c22 }");
}

#[test]
fn test_nested_generics() {
    let generic: NestedGeneric<Vec<u8>> = NestedGeneric {
        contents: Generic {
            contents: vec![8, 34],
        },
    };

    encode_decode_tests!(
        NestedGeneric<Vec<u8>>,
        generic,
        "{ contents: { contents: 0x0822 } }"
    );
}

#[test]
fn test_nested_silent_fields() {
    let my_call = WithSilentField {
        int: 123,
        skipped: WithSilentField {
            int: 456,
            skipped: 789,
            str: "hi".try_into().unwrap(),
            phantom: Default::default(),
        },
        phantom: PhantomData,
        str: "this should be included".try_into().unwrap(),
    };

    encode_decode_tests!(
        WithSilentField<WithSilentField<i32>>,
        my_call,
        "{ int: 123, str: \"this should be included\" }"
    );
}
