use iref::Uri;
use ssi_claims_core::{DateTimeProvider, Eip712TypesLoaderProvider, ResolverProvider};
use ssi_json_ld::JsonLdLoaderProvider;
use ssi_jwk::JWKResolver;
use ssi_verification_methods::{AnyMethod, VerificationMethodResolver};
use crate::{
    bitstring_status_list::{
        self, BitstringStatusListCredential, BitstringStatusListEntry,
        BitstringStatusListEntrySetCredential,
    },
    token_status_list::{self, StatusListToken},
    EncodedStatusMap, FromBytes, FromBytesOptions, StatusMap, StatusMapEntry, StatusMapEntrySet,
    StatusSizeError,
};
pub enum AnyStatusMap {
    BitstringStatusList(BitstringStatusListCredential),
    TokenStatusList(StatusListToken),
}
impl AnyStatusMap {
    pub fn credential_url(&self) -> Option<&Uri> {
        match self {
            Self::BitstringStatusList(cred) => cred.id.as_deref(),
            Self::TokenStatusList(_) => None,
        }
    }
}
#[derive(Debug, thiserror::Error)]
pub enum FromBytesError {
    #[error("unexpected media type `{0}`")]
    UnexpectedMediaType(String),
    #[error(transparent)]
    BitstringStatusList(bitstring_status_list::FromBytesError),
    #[error(transparent)]
    TokenStatusList(token_status_list::FromBytesError),
}
impl<V> FromBytes<V> for AnyStatusMap
where
    V: ResolverProvider + DateTimeProvider + JsonLdLoaderProvider + Eip712TypesLoaderProvider,
    V::Resolver: JWKResolver + VerificationMethodResolver<Method = AnyMethod>,
{
    type Error = FromBytesError;
    async fn from_bytes_with(
        bytes: &[u8],
        media_type: &str,
        verifier: &V,
        options: FromBytesOptions,
    ) -> Result<Self, Self::Error> {
        match media_type {
            "statuslist+jwt" | "statuslist+cwt" => {
                StatusListToken::from_bytes_with(bytes, media_type, verifier, options)
                    .await
                    .map(AnyStatusMap::TokenStatusList)
                    .map_err(FromBytesError::TokenStatusList)
            }
            "application/vc+ld+json+jwt"
            | "application/vc+ld+json+sd-jwt"
            | "application/vc+ld+json+cose"
            | "application/vc+ld+json" => {
                BitstringStatusListCredential::from_bytes_with(bytes, media_type, verifier, options)
                    .await
                    .map(AnyStatusMap::BitstringStatusList)
                    .map_err(FromBytesError::BitstringStatusList)
            }
            other => Err(FromBytesError::UnexpectedMediaType(other.to_owned())),
        }
    }
}
#[derive(Debug, thiserror::Error)]
pub enum DecodeError {
    #[error(transparent)]
    BitstringStatusList(#[from] bitstring_status_list::DecodeError),
    #[error(transparent)]
    TokenStatusList(#[from] token_status_list::DecodeError),
}
impl EncodedStatusMap for AnyStatusMap {
    type DecodeError = DecodeError;
    type Decoded = AnyDecodedStatusMap;
    fn decode(self) -> Result<Self::Decoded, Self::DecodeError> {
        match self {
            Self::BitstringStatusList(m) => m
                .decode()
                .map(AnyDecodedStatusMap::BitstringStatusList)
                .map_err(Into::into),
            Self::TokenStatusList(m) => m
                .decode()
                .map(AnyDecodedStatusMap::TokenStatusList)
                .map_err(Into::into),
        }
    }
}
#[derive(Clone)]
pub enum AnyDecodedStatusMap {
    BitstringStatusList(bitstring_status_list::StatusList),
    TokenStatusList(token_status_list::StatusList),
}
impl AnyDecodedStatusMap {
    pub fn iter(
        &self,
        status_size: Option<u8>,
    ) -> Result<AnyDecodedStatusMapIter, StatusSizeError> {
        match self {
            Self::BitstringStatusList(m) => Ok(AnyDecodedStatusMapIter::BitstringStatusList(
                m.iter(status_size.ok_or(StatusSizeError::Missing)?.try_into()?),
            )),
            Self::TokenStatusList(m) => Ok(AnyDecodedStatusMapIter::TokenStatusList(m.iter())),
        }
    }
}
impl StatusMap for AnyDecodedStatusMap {
    type Key = usize;
    type StatusSize = u8;
    type Status = u8;
    fn time_to_live(&self) -> Option<std::time::Duration> {
        match self {
            Self::BitstringStatusList(m) => m.time_to_live(),
            Self::TokenStatusList(m) => m.time_to_live(),
        }
    }
    fn get_by_key(
        &self,
        status_size: Option<u8>,
        key: Self::Key,
    ) -> Result<Option<Self::Status>, StatusSizeError> {
        match self {
            Self::BitstringStatusList(m) => {
                m.get_by_key(status_size.map(TryInto::try_into).transpose()?, key)
            }
            Self::TokenStatusList(m) => {
                m.get_by_key(status_size.map(TryInto::try_into).transpose()?, key)
            }
        }
    }
}
pub enum AnyDecodedStatusMapIter<'a> {
    BitstringStatusList(bitstring_status_list::BitStringIter<'a>),
    TokenStatusList(token_status_list::BitStringIter<'a>),
}
impl<'a> Iterator for AnyDecodedStatusMapIter<'a> {
    type Item = u8;
    fn next(&mut self) -> Option<Self::Item> {
        match self {
            Self::BitstringStatusList(i) => i.next(),
            Self::TokenStatusList(i) => i.next(),
        }
    }
}
#[derive(Debug, thiserror::Error)]
pub enum EntrySetFromBytesError {
    #[error(transparent)]
    TokenStatusList(#[from] token_status_list::EntrySetFromBytesError),
    #[error(transparent)]
    BitstringStatusList(#[from] bitstring_status_list::FromBytesError),
    #[error("unexpected media type `{0}`")]
    UnexpectedMediaType(String),
}
pub enum AnyEntrySet {
    BitstringStatusList(BitstringStatusListEntrySetCredential),
    TokenStatusList(token_status_list::AnyStatusListEntrySet),
}
impl<V> FromBytes<V> for AnyEntrySet
where
    V: ResolverProvider + DateTimeProvider + JsonLdLoaderProvider + Eip712TypesLoaderProvider,
    V::Resolver: JWKResolver + VerificationMethodResolver<Method = AnyMethod>,
{
    type Error = EntrySetFromBytesError;
    async fn from_bytes_with(
        bytes: &[u8],
        media_type: &str,
        params: &V,
        options: FromBytesOptions,
    ) -> Result<Self, Self::Error> {
        match media_type {
            "application/json" | "application/jwt" | "application/cbor" | "application/cwt" => {
                token_status_list::AnyStatusListEntrySet::from_bytes_with(
                    bytes, media_type, params, options,
                )
                .await
                .map(Self::TokenStatusList)
                .map_err(Into::into)
            }
            "application/vc+ld+json+jwt"
            | "application/vc+ld+json+sd-jwt"
            | "application/vc+ld+json+cose"
            | "application/vc+ld+json" => {
                bitstring_status_list::BitstringStatusListEntrySetCredential::from_bytes_with(
                    bytes, media_type, params, options,
                )
                .await
                .map(Self::BitstringStatusList)
                .map_err(Into::into)
            }
            other => Err(EntrySetFromBytesError::UnexpectedMediaType(
                other.to_owned(),
            )),
        }
    }
}
impl StatusMapEntrySet for AnyEntrySet {
    type Entry<'a> = AnyStatusMapEntryRef<'a> where Self: 'a;
    fn get_entry(&self, purpose: crate::StatusPurpose<&str>) -> Option<Self::Entry<'_>> {
        match self {
            Self::BitstringStatusList(s) => s
                .get_entry(purpose)
                .map(AnyStatusMapEntryRef::BitstringStatusList),
            Self::TokenStatusList(s) => s
                .get_entry(purpose)
                .map(AnyStatusMapEntryRef::TokenStatusList),
        }
    }
}
pub enum AnyStatusMapEntryRef<'a> {
    BitstringStatusList(&'a BitstringStatusListEntry),
    TokenStatusList(token_status_list::AnyStatusListReference<'a>),
}
impl<'a> StatusMapEntry for AnyStatusMapEntryRef<'a> {
    type Key = usize;
    type StatusSize = u8;
    fn status_list_url(&self) -> &Uri {
        match self {
            Self::BitstringStatusList(e) => e.status_list_url(),
            Self::TokenStatusList(e) => e.status_list_url(),
        }
    }
    fn key(&self) -> Self::Key {
        match self {
            Self::BitstringStatusList(e) => e.key(),
            Self::TokenStatusList(e) => e.key(),
        }
    }
    fn status_size(&self) -> Option<Self::StatusSize> {
        match self {
            Self::BitstringStatusList(e) => e.status_size().map(Into::into),
            Self::TokenStatusList(e) => e.status_size().map(Into::into),
        }
    }
}