use jacquard_common::deps::smol_str::{SmolStr, ToSmolStr};
use jacquard_common::types::string::{Did, Handle, UriValue};
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 {
    /// Session not yet determined — the initial state before `getSession`
    /// resolves. Distinct from `Anonymous` so the UI can show a loading state
    /// instead of flashing signed-out chrome at users who are actually signed in.
    #[default]
    Unknown,
    Anonymous,
    Authenticated(AuthenticatedIdentity),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct AuthenticatedIdentity {
    pub avatar: Option<UriValue>,
    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) => {
                let avatar = session.profile.avatar.clone();
                Self::Authenticated(AuthenticatedIdentity {
                    avatar,
                    did: session.did,
                    handle: session.handle,
                    display_name: profile_display_name(&session.profile),
                })
            }
        }
    }
}

impl AuthenticatedIdentity {
    pub fn label(&self) -> SmolStr {
        self.display_name
            .clone()
            .unwrap_or_else(|| self.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,
                    lists: None,
                    printers: None,
                    record: jacquard::Data::Null,
                    follower_count: None,
                    following_count: None,
                    total_likes: None,
                    viewer: None,
                },
                extra_data: None,
                authenticated: true,
            })),
            extra_data: None,
        };

        let identity = SessionIdentity::from_get_session(output);
        match identity {
            SessionIdentity::Authenticated(auth) => assert_eq!(auth.label(), "Alice Maker"),
            _ => panic!("expected authenticated identity"),
        }
    }
    #[test]
    fn authenticated_get_session_retains_avatar() {
        let output = GetSessionOutput {
            value: SessionView::SessionAuthenticated(Box::new(SessionAuthenticated {
                did: Did::new_owned("did:plc:bob").unwrap(),
                handle: Handle::new_owned("bob.test").unwrap(),
                profile: ProfileView {
                    avatar: Some(UriValue::new_owned("https://cdn.example/bob.jpg").unwrap()),
                    default_license: None,
                    indexed_at: None,
                    description: None,
                    did: Did::new_owned("did:plc:bob").unwrap(),
                    display_name: None,
                    handle: "bob.test".into(),
                    pronouns: None,
                    thing_count: None,
                    extra_data: None,
                    links: None,
                    lists: None,
                    printers: None,
                    record: jacquard::Data::Null,
                    follower_count: None,
                    following_count: None,
                    total_likes: None,
                    viewer: None,
                },
                extra_data: None,
                authenticated: true,
            })),
            extra_data: None,
        };

        match SessionIdentity::from_get_session(output) {
            SessionIdentity::Authenticated(identity) => {
                assert_eq!(
                    identity.avatar.as_ref().map(AsRef::as_ref),
                    Some("https://cdn.example/bob.jpg")
                );
            }
            _ => panic!("expected authenticated identity"),
        }
    }
    #[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();
        match SessionIdentity::from_get_session(output) {
            SessionIdentity::Authenticated(auth) => assert_eq!(auth.label(), "Alice Maker"),
            _ => panic!("expected authenticated identity"),
        }
    }
}
