use serde::{Deserialize, Serialize};
use std::collections::HashMap;

use super::Title;

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct Timeline {
    pub entries: Vec<Entry>,
    pub users: HashMap<String, TimelineUser>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TimelineUser {
    pub id: String,
    pub name: String,
    /// Only appears if the user is a titled player or a bot user
    pub title: Option<Title>,
    /// See available flair list and images: https://github.com/lichess-org/lila/tree/master/public/flair
    pub flair: Option<String>,
    #[serde(default)]
    #[deprecated(note = "Use patron_color value instead to determine if player is a patron.")]
    pub patron: Option<bool>,
    /// Players can choose a color for their Patron wings. The presence of this field indicates the player is an active Patron.
    /// See here for the color mappings: https://github.com/lichess-org/lila/blob/master/ui/lib/css/abstract/_patron-colors.scss
    pub patron_color: Option<u8>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(tag = "type")]
#[serde(rename_all = "kebab-case")]
pub enum Entry {
    Follow { date: f64, data: FollowData },
    TeamJoin { date: f64, data: TeamJoinData },
    TeamCreate { date: f64, data: TeamCreateData },
    ForumPost { date: f64, data: ForumPostData },
    BlogPost { date: f64, data: BlogPostData },
    UblogPost { date: f64, data: UblogPostData },
    TourJoin { date: f64, data: TourJoinData },
    GameEnd { date: f64, data: GameEndData },
    SimulCreate { date: f64, data: SimulData },
    SimulJoin { date: f64, data: SimulData },
    StudyLike { date: f64, data: StudyLikeData },
    PlanStart { date: f64, data: PlanStartData },
    PlanRenew { date: f64, data: PlanRenewData },
    UblogPostLike { date: f64, data: UblogPostLikeData },
    StreamStart { date: f64, data: StreamStartData },
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct FollowData {
    pub u1: String,
    pub u2: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamJoinData {
    pub user_id: String,
    pub team_id: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TeamCreateData {
    pub user_id: String,
    pub team_id: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ForumPostData {
    pub user_id: String,
    pub topic_id: String,
    pub topic_name: String,
    pub post_id: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BlogPostData {
    pub id: String,
    pub slug: String,
    pub title: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UblogPostData {
    pub user_id: String,
    pub id: String,
    pub slug: String,
    pub title: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TourJoinData {
    pub user_id: String,
    pub tour_id: String,
    pub tour_name: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GameEndData {
    pub full_id: String,
    pub opponent: String,
    pub win: bool,
    pub perf: Perf,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct SimulData {
    pub user_id: String,
    pub simul_id: String,
    pub simul_name: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct StudyLikeData {
    pub user_id: String,
    pub study_id: String,
    pub study_name: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlanStartData {
    pub user_id: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct PlanRenewData {
    pub user_id: String,
    pub months: f64,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct UblogPostLikeData {
    pub user_id: String,
    pub id: String,
    pub title: String,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct StreamStartData {
    pub id: String,
    pub title: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Perf {
    UltraBullet,
    Bullet,
    Blitz,
    Rapid,
    Classical,
    Correspondence,
    Chess960,
    Crazyhouse,
    Antichess,
    Atomic,
    Horde,
    KingOfTheHill,
    RacingKings,
    ThreeCheck,
}

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

    #[test]
    fn test_timeline_deserialization() {
        let json = include_str!("../../test_data/account/timeline.json");

        let timeline: Timeline = serde_json::from_str(json).expect("Failed to deserialize");

        assert_eq!(timeline.entries.len(), 15);
        assert_eq!(timeline.users.len(), 18);

        // Check first entry: follow
        if let Entry::Follow { date, data } = &timeline.entries[0] {
            assert_eq!(*date, 1765642396541.0);
            assert_eq!(data.u1, "yunel");
            assert_eq!(data.u2, "abdul");
        } else {
            panic!("First entry should be Follow");
        }

        // Check another follow
        if let Entry::Follow { date, data } = &timeline.entries[1] {
            assert_eq!(*date, 1765636142888.0);
            assert_eq!(data.u1, "benjamin");
            assert_eq!(data.u2, "elizabeth");
        } else {
            panic!("Second entry should be Follow");
        }

        // Check team-join entry
        if let Entry::TeamJoin { date, data } = &timeline.entries[7] {
            assert_eq!(*date, 1765582445430.0);
            assert_eq!(data.user_id, "yarah");
            assert_eq!(data.team_id, "captain-james-t-pirc");
        } else {
            panic!("Eighth entry should be TeamJoin");
        }

        // Check another team-join
        if let Entry::TeamJoin { date, data } = &timeline.entries[8] {
            assert_eq!(*date, 1765582145930.0);
            assert_eq!(data.user_id, "vera");
            assert_eq!(
                data.team_id,
                "i-dont-think-he-knows-about-second-piece-sac-pip"
            );
        } else {
            panic!("Ninth entry should be TeamJoin");
        }

        // Check users
        assert!(timeline.users.contains_key("bobby"));
        let bobby = &timeline.users["bobby"];
        assert_eq!(bobby.id, "bobby");
        assert_eq!(bobby.name, "Bobby");
        assert_eq!(bobby.flair, Some("symbols.white-flag".to_string()));
        #[allow(deprecated)]
        {
            assert_eq!(bobby.patron, Some(true));
        }
        assert_eq!(bobby.patron_color, Some(1));
        assert!(bobby.title.is_none());

        assert!(timeline.users.contains_key("angel"));
        let angel = &timeline.users["angel"];
        assert_eq!(angel.title, Some(Title::WCM));
        assert_eq!(
            angel.flair,
            Some("people.woman-factory-worker-dark-skin-tone".to_string())
        );

        assert!(timeline.users.contains_key("bot9"));
        let bot9 = &timeline.users["bot9"];
        assert_eq!(bot9.title, Some(Title::BOT));
        assert!(bot9.flair.is_none());
    }
}
