use std::{ops::Deref, sync::Arc};

use serde::{Deserialize, Serialize};

/// Cheap-to-clone handle to per-session user data.
#[derive(Debug, Clone)]
pub struct UserState(Arc<UserStateInner>);

impl Deref for UserState {
    type Target = UserStateInner;
    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl From<UserStateInner> for UserState {
    fn from(value: UserStateInner) -> Self {
        Self(Arc::new(value))
    }
}

/// Per-session user data stored in the tower-sessions session store.
///
/// Persisted across requests for the lifetime of the session.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct UserStateInner {
    /// Subject identifier from Keycloak (unique user ID).
    pub sub: String,
    /// OAuth2 access token.
    pub access_token: String,
    /// OAuth2 refresh token.
    pub refresh_token: String,
    /// Basic user profile.
    pub user: User,
}

/// Basic user profile stored alongside the session.
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct User {
    /// Email address.
    pub email: String,
    /// Display name (preferred_username or full name from Keycloak).
    pub name: String,
    /// Avatar / profile picture URL.
    pub avatar_url: String,
}

#[cfg(test)]
mod tests {
    use super::*;
    use pretty_assertions::assert_eq;

    #[test]
    fn user_state_inner_default_has_empty_strings() {
        let inner = UserStateInner::default();
        assert_eq!(inner.sub, "");
        assert_eq!(inner.access_token, "");
        assert_eq!(inner.refresh_token, "");
        assert_eq!(inner.user.email, "");
        assert_eq!(inner.user.name, "");
        assert_eq!(inner.user.avatar_url, "");
    }

    #[test]
    fn user_default_has_empty_strings() {
        let user = User::default();
        assert_eq!(user.email, "");
        assert_eq!(user.name, "");
        assert_eq!(user.avatar_url, "");
    }

    #[test]
    fn user_state_inner_serde_round_trip() {
        let inner = UserStateInner {
            sub: "user-123".into(),
            access_token: "tok-abc".into(),
            refresh_token: "ref-xyz".into(),
            user: User {
                email: "a@b.com".into(),
                name: "Alice".into(),
                avatar_url: "https://img.example.com/a.png".into(),
            },
        };
        let json = serde_json::to_string(&inner).expect("serialize UserStateInner");
        let back: UserStateInner = serde_json::from_str(&json).expect("deserialize UserStateInner");
        assert_eq!(inner.sub, back.sub);
        assert_eq!(inner.access_token, back.access_token);
        assert_eq!(inner.refresh_token, back.refresh_token);
        assert_eq!(inner.user.email, back.user.email);
        assert_eq!(inner.user.name, back.user.name);
        assert_eq!(inner.user.avatar_url, back.user.avatar_url);
    }

    #[test]
    fn user_state_from_inner_and_deref() {
        let inner = UserStateInner {
            sub: "sub-1".into(),
            access_token: "at".into(),
            refresh_token: "rt".into(),
            user: User {
                email: "e@e.com".into(),
                name: "Eve".into(),
                avatar_url: "".into(),
            },
        };
        let state = UserState::from(inner);
        // Deref should give access to inner fields
        assert_eq!(state.sub, "sub-1");
        assert_eq!(state.user.name, "Eve");
    }

    #[test]
    fn user_serde_round_trip() {
        let user = User {
            email: "bob@test.com".into(),
            name: "Bob".into(),
            avatar_url: "https://avatars.io/bob".into(),
        };
        let json = serde_json::to_string(&user).expect("serialize User");
        let back: User = serde_json::from_str(&json).expect("deserialize User");
        assert_eq!(user.email, back.email);
        assert_eq!(user.name, back.name);
        assert_eq!(user.avatar_url, back.avatar_url);
    }

    #[test]
    fn user_state_clone_is_cheap() {
        let inner = UserStateInner::default();
        let state = UserState::from(inner);
        let cloned = state.clone();
        // Both point to the same Arc allocation
        assert_eq!(state.sub, cloned.sub);
    }
}
