use std::str::FromStr;

use af_sui_types::{Address, IdentStr, Identifier, StructTag, TypeTag};
use serde::{Deserialize, Serialize};

use crate::{
    MoveStruct,
    MoveType,
    ParseStructTagError,
    StaticAddress,
    StaticModule,
    StaticName,
    StaticStructTag as _,
    StaticTypeParams,
    StaticTypeTag,
    StructTagError,
    TypeParamsError,
    TypeTagError,
};

#[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, PartialOrd, Ord, Serialize)]
pub struct StringTypeTag;

impl From<StringTypeTag> for TypeTag {
    fn from(value: StringTypeTag) -> Self {
        Self::Struct(Box::new(value.into()))
    }
}

impl TryFrom<TypeTag> for StringTypeTag {
    type Error = TypeTagError;

    fn try_from(value: TypeTag) -> Result<Self, Self::Error> {
        match value {
            TypeTag::Struct(stag) => Ok((*stag).try_into()?),
            other => Err(TypeTagError::Variant {
                expected: "Struct(_)".to_owned(),
                got: other,
            }),
        }
    }
}

impl From<StringTypeTag> for StructTag {
    fn from(_: StringTypeTag) -> Self {
        String::struct_tag()
    }
}

impl TryFrom<StructTag> for StringTypeTag {
    type Error = StructTagError;

    fn try_from(value: StructTag) -> Result<Self, Self::Error> {
        use StructTagError::*;
        let expected = String::struct_tag();
        if *value.address() != *expected.address() {
            return Err(Address {
                expected: *expected.address(),
                got: *value.address(),
            });
        }
        if value.module() != expected.module() {
            return Err(Module {
                expected: expected.module().clone(),
                got: value.module().clone(),
            });
        }
        if value.name() != expected.name() {
            return Err(Name {
                expected: expected.name().clone(),
                got: value.name().clone(),
            });
        }
        if !value.type_params().is_empty() {
            return Err(TypeParams(TypeParamsError::Number {
                expected: 0,
                got: value.type_params().len(),
            }));
        }
        Ok(Self)
    }
}

impl FromStr for StringTypeTag {
    type Err = ParseStructTagError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let stag: StructTag = s.parse()?;
        Ok(stag.try_into()?)
    }
}

impl MoveType for String {
    type TypeTag = StringTypeTag;
}

impl MoveStruct for String {
    type StructTag = StringTypeTag;
}

impl StaticTypeTag for String {
    fn type_() -> Self::TypeTag {
        StringTypeTag {}
    }
}

impl StaticAddress for String {
    fn address() -> Address {
        Address::from_static("0x1")
    }
}

impl StaticModule for String {
    fn module() -> Identifier {
        IdentStr::cast("string").to_owned()
    }
}

impl StaticName for String {
    fn name() -> Identifier {
        IdentStr::cast("String").to_owned()
    }
}

impl StaticTypeParams for String {
    fn type_params() -> Vec<TypeTag> {
        vec![]
    }
}
