use jacquard_common::deps::smol_str::{SmolStr, ToSmolStr};
use jacquard_common::types::string::{Did, Handle};
use polymodel_api::space_polymodel::actor::ProfileView;
use polymodel_api::space_polymodel::actor::get_session::{GetSessionOutput, SessionView};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub enum SessionIdentity {
    #[default]
    Anonymous,
    Authenticated(AuthenticatedIdentity),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthenticatedIdentity {
    pub did: Did,
    pub handle: Handle,
    pub display_name: Option<SmolStr>,
}

impl SessionIdentity {
    pub fn from_get_session(output: GetSessionOutput) -> Self {
        match output.value {
            SessionView::SessionUnauthenticated(_) => Self::Anonymous,
            SessionView::SessionAuthenticated(session) => {
                Self::Authenticated(AuthenticatedIdentity {
                    did: session.did,
                    handle: session.handle,
                    display_name: profile_display_name(&session.profile),
                })
            }
        }
    }

    pub fn label(&self) -> SmolStr {
        match self {
            Self::Anonymous => SmolStr::new_static("Signed out"),
            Self::Authenticated(identity) => identity
                .display_name
                .clone()
                .unwrap_or_else(|| identity.handle.as_str().to_smolstr()),
        }
    }
}

fn profile_display_name(profile: &ProfileView) -> Option<SmolStr> {
    profile
        .display_name
        .as_ref()
        .map(|value| value.to_smolstr())
        .filter(|value| !value.trim().is_empty())
}

#[cfg(test)]
mod tests {
    use super::*;
    use jacquard_common::types::did::Did;
    use polymodel_api::space_polymodel::actor::ProfileView;
    use polymodel_api::space_polymodel::actor::get_session::{
        SessionAuthenticated, SessionUnauthenticated,
    };
    #[test]
    fn unauthenticated_get_session_maps_to_anonymous() {
        let output = GetSessionOutput {
            value: SessionView::SessionUnauthenticated(Box::new(SessionUnauthenticated {
                authenticated: false,
                extra_data: None,
            })),
            extra_data: None,
        };

        assert_eq!(
            SessionIdentity::from_get_session(output),
            SessionIdentity::Anonymous
        );
    }

    #[test]
    fn authenticated_get_session_maps_identity_label() {
        let output = GetSessionOutput {
            value: SessionView::SessionAuthenticated(Box::new(SessionAuthenticated {
                did: Did::new_owned("did:plc:alice").unwrap(),
                handle: Handle::new_owned("alice.test").unwrap(),
                profile: ProfileView {
                    avatar: None,
                    default_license: None,
                    indexed_at: None,
                    description: None,
                    did: Did::new_owned("did:plc:alice").unwrap(),
                    display_name: Some("Alice Maker".into()),
                    handle: "alice.test".into(),
                    pronouns: None,
                    thing_count: None,
                    extra_data: None,
                    links: None,
                    printers: None,
                    record: jacquard::Data::Null,
                    total_likes: None,
                },
                extra_data: None,
                authenticated: true,
            })),
            extra_data: None,
        };

        let identity = SessionIdentity::from_get_session(output);
        assert!(matches!(identity, SessionIdentity::Authenticated(_)));
        assert_eq!(identity.label(), "Alice Maker");
    }
    #[test]
    fn get_session_json_decodes_unauthenticated_union_arm() {
        let output: GetSessionOutput = serde_json::from_str(r#"{"$type":"space.polymodel.actor.getSession#sessionUnauthenticated","authenticated":false}"#).unwrap();
        assert_eq!(
            SessionIdentity::from_get_session(output),
            SessionIdentity::Anonymous
        );
    }
    #[test]
    fn get_session_json_decodes_authenticated_union_arm() {
        let output: GetSessionOutput = serde_json::from_str(r#"{"$type":"space.polymodel.actor.getSession#sessionAuthenticated","authenticated":true,"did":"did:plc:alice","handle":"alice.test","profile":{"avatar":null,"description":null,"did":"did:plc:alice","displayName":"Alice Maker","handle":"alice.test","record":null}}"#).unwrap();
        assert_eq!(
            SessionIdentity::from_get_session(output).label(),
            "Alice Maker"
        );
    }
}
