use serde_json::Value;
use tail_fin_common::time::unix_timestamp_to_string;

use crate::types::{
    RedditComment, RedditCommentsListing, RedditListing, RedditPost, RedditThread, RedditUser,
};

/// Parse a single Reddit post from a `t3` data object.
fn parse_post(data: &Value) -> Option<RedditPost> {
    let id = data.get("id").and_then(|v| v.as_str())?.to_string();
    let title = data
        .get("title")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let author = data
        .get("author")
        .and_then(|v| v.as_str())
        .unwrap_or("[deleted]")
        .to_string();
    let subreddit = data
        .get("subreddit")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let score = data.get("score").and_then(|v| v.as_i64()).unwrap_or(0);
    let num_comments = data
        .get("num_comments")
        .and_then(|v| v.as_u64())
        .unwrap_or(0);
    let url = data
        .get("url")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let selftext = data
        .get("selftext")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let created = data
        .get("created_utc")
        .and_then(|v| v.as_f64())
        .unwrap_or(0.0);

    Some(RedditPost {
        id,
        title,
        author,
        subreddit,
        score,
        num_comments,
        url,
        selftext,
        created_at: unix_timestamp_to_string(created),
    })
}

/// Parse a Reddit listing response (hot, frontpage, popular, subreddit, user-posts, saved, upvoted).
pub fn parse_listing(data: &Value, limit: usize) -> RedditListing {
    let listing_data = data.get("data");

    let after = listing_data
        .and_then(|d| d.get("after"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string());

    let children = listing_data
        .and_then(|d| d.get("children"))
        .and_then(|v| v.as_array());

    let posts = match children {
        Some(c) => c
            .iter()
            .filter_map(|child| {
                let kind = child.get("kind").and_then(|v| v.as_str()).unwrap_or("");
                if kind == "t3" {
                    parse_post(child.get("data")?)
                } else {
                    None
                }
            })
            .take(limit)
            .collect(),
        None => vec![],
    };

    RedditListing { posts, after }
}

/// Parse a Reddit user about response.
pub fn parse_user(data: &Value) -> Option<RedditUser> {
    let d = data.get("data").unwrap_or(data);
    let name = d.get("name").and_then(|v| v.as_str())?.to_string();
    let link_karma = d.get("link_karma").and_then(|v| v.as_i64()).unwrap_or(0);
    let comment_karma = d.get("comment_karma").and_then(|v| v.as_i64()).unwrap_or(0);
    let karma = link_karma + comment_karma;
    let created = d.get("created_utc").and_then(|v| v.as_f64()).unwrap_or(0.0);
    let is_gold = d.get("is_gold").and_then(|v| v.as_bool()).unwrap_or(false);

    Some(RedditUser {
        name,
        karma,
        created_at: unix_timestamp_to_string(created),
        is_gold,
    })
}

/// Parse a Reddit post detail response (/comments/{id}.json).
///
/// Reddit returns an array: [post_listing, comments_listing].
pub fn parse_thread(data: &Value, depth: u32) -> Option<RedditThread> {
    let arr = data.as_array()?;
    if arr.len() < 2 {
        return None;
    }

    let post_data = arr[0].pointer("/data/children/0/data")?;
    let post = parse_post(post_data)?;

    let children = arr[1]
        .pointer("/data/children")
        .and_then(|v| v.as_array())
        .cloned()
        .unwrap_or_default();

    let mut comments = Vec::new();
    for child in &children {
        if child.get("kind").and_then(|v| v.as_str()) == Some("t1") {
            if let Some(data) = child.get("data") {
                flatten_comments(data, 0, depth, &mut comments);
            }
        }
    }

    Some(RedditThread { post, comments })
}

/// Recursively flatten nested comment tree into a flat list with depth info.
fn flatten_comments(
    data: &Value,
    current_depth: u32,
    max_depth: u32,
    out: &mut Vec<RedditComment>,
) {
    if current_depth > max_depth {
        return;
    }

    let id = data
        .get("id")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let author = data
        .get("author")
        .and_then(|v| v.as_str())
        .unwrap_or("[deleted]")
        .to_string();
    let body = data
        .get("body")
        .and_then(|v| v.as_str())
        .unwrap_or("")
        .to_string();
    let score = data.get("score").and_then(|v| v.as_i64()).unwrap_or(0);
    let created = data
        .get("created_utc")
        .and_then(|v| v.as_f64())
        .unwrap_or(0.0);

    if !id.is_empty() {
        out.push(RedditComment {
            id,
            author,
            body,
            score,
            created_at: unix_timestamp_to_string(created),
            depth: current_depth,
        });
    }

    if let Some(replies) = data
        .pointer("/replies/data/children")
        .and_then(|v| v.as_array())
    {
        for reply in replies {
            if reply.get("kind").and_then(|v| v.as_str()) == Some("t1") {
                if let Some(reply_data) = reply.get("data") {
                    flatten_comments(reply_data, current_depth + 1, max_depth, out);
                }
            }
        }
    }
}

/// Parse a user comments listing (/user/{name}/comments.json).
pub fn parse_comments_listing(data: &Value, limit: usize) -> RedditCommentsListing {
    let listing_data = data.get("data");

    let after = listing_data
        .and_then(|d| d.get("after"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string());

    let children = listing_data
        .and_then(|d| d.get("children"))
        .and_then(|v| v.as_array());

    let comments = match children {
        Some(c) => c
            .iter()
            .filter_map(|child| {
                let kind = child.get("kind").and_then(|v| v.as_str()).unwrap_or("");
                if kind != "t1" {
                    return None;
                }
                let d = child.get("data")?;
                let id = d.get("id").and_then(|v| v.as_str())?.to_string();
                let author = d
                    .get("author")
                    .and_then(|v| v.as_str())
                    .unwrap_or("[deleted]")
                    .to_string();
                let body = d
                    .get("body")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();
                let score = d.get("score").and_then(|v| v.as_i64()).unwrap_or(0);
                let created = d.get("created_utc").and_then(|v| v.as_f64()).unwrap_or(0.0);

                Some(RedditComment {
                    id,
                    author,
                    body,
                    score,
                    created_at: unix_timestamp_to_string(created),
                    depth: 0,
                })
            })
            .take(limit)
            .collect(),
        None => vec![],
    };

    RedditCommentsListing { comments, after }
}

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

    #[test]
    fn test_parse_listing_basic() {
        let data = serde_json::json!({
            "data": {
                "children": [{
                    "kind": "t3",
                    "data": {
                        "id": "abc123",
                        "title": "Test Post",
                        "author": "testuser",
                        "subreddit": "rust",
                        "score": 42,
                        "num_comments": 10,
                        "url": "https://example.com",
                        "selftext": "Hello world",
                        "created_utc": 1700000000.0
                    }
                }]
            }
        });

        let resp = parse_listing(&data, 25);
        assert_eq!(resp.posts.len(), 1);
        assert_eq!(resp.posts[0].id, "abc123");
        assert_eq!(resp.posts[0].title, "Test Post");
        assert_eq!(resp.posts[0].author, "testuser");
        assert_eq!(resp.posts[0].subreddit, "rust");
        assert_eq!(resp.posts[0].score, 42);
        assert_eq!(resp.posts[0].num_comments, 10);
    }

    #[test]
    fn test_parse_listing_empty() {
        let data = serde_json::json!({ "data": { "children": [] } });
        assert!(parse_listing(&data, 25).posts.is_empty());
    }

    #[test]
    fn test_parse_listing_respects_limit() {
        let data = serde_json::json!({
            "data": {
                "children": [
                    { "kind": "t3", "data": { "id": "a", "title": "A", "created_utc": 0.0 } },
                    { "kind": "t3", "data": { "id": "b", "title": "B", "created_utc": 0.0 } },
                    { "kind": "t3", "data": { "id": "c", "title": "C", "created_utc": 0.0 } },
                ]
            }
        });
        assert_eq!(parse_listing(&data, 2).posts.len(), 2);
    }

    #[test]
    fn test_parse_listing_skips_non_t3() {
        let data = serde_json::json!({
            "data": {
                "children": [
                    { "kind": "t1", "data": { "id": "comment1" } },
                    { "kind": "t3", "data": { "id": "post1", "title": "Post", "created_utc": 0.0 } },
                ]
            }
        });
        let resp = parse_listing(&data, 25);
        assert_eq!(resp.posts.len(), 1);
        assert_eq!(resp.posts[0].id, "post1");
    }

    #[test]
    fn test_parse_listing_missing_data() {
        let data = serde_json::json!({ "error": 404 });
        assert!(parse_listing(&data, 25).posts.is_empty());
    }

    #[test]
    fn test_parse_user_basic() {
        let data = serde_json::json!({
            "kind": "t2",
            "data": {
                "name": "spez",
                "link_karma": 100000,
                "comment_karma": 50000,
                "created_utc": 1600000000.0,
                "is_gold": true
            }
        });
        let user = parse_user(&data).unwrap();
        assert_eq!(user.name, "spez");
        assert_eq!(user.karma, 150000);
        assert!(user.is_gold);
    }

    #[test]
    fn test_parse_user_missing() {
        let data = serde_json::json!({});
        assert!(parse_user(&data).is_none());
    }

    #[test]
    fn test_parse_thread_basic() {
        let data = serde_json::json!([
            {
                "data": {
                    "children": [{
                        "kind": "t3",
                        "data": {
                            "id": "post1", "title": "My Post", "author": "op",
                            "subreddit": "rust", "score": 100, "num_comments": 2,
                            "url": "https://example.com", "selftext": "Body",
                            "created_utc": 1700000000.0
                        }
                    }]
                }
            },
            {
                "data": {
                    "children": [{
                        "kind": "t1",
                        "data": {
                            "id": "c1", "author": "commenter1", "body": "Great!",
                            "score": 10, "created_utc": 1700001000.0,
                            "replies": {
                                "data": {
                                    "children": [{
                                        "kind": "t1",
                                        "data": {
                                            "id": "c2", "author": "commenter2",
                                            "body": "Agreed!", "score": 5,
                                            "created_utc": 1700002000.0, "replies": ""
                                        }
                                    }]
                                }
                            }
                        }
                    }]
                }
            }
        ]);

        let thread = parse_thread(&data, 5).unwrap();
        assert_eq!(thread.post.id, "post1");
        assert_eq!(thread.comments.len(), 2);
        assert_eq!(thread.comments[0].id, "c1");
        assert_eq!(thread.comments[0].depth, 0);
        assert_eq!(thread.comments[1].id, "c2");
        assert_eq!(thread.comments[1].depth, 1);
    }

    #[test]
    fn test_parse_thread_respects_depth() {
        let data = serde_json::json!([
            { "data": { "children": [{ "kind": "t3", "data": { "id": "p1", "title": "T", "created_utc": 0.0 } }] } },
            { "data": { "children": [{
                "kind": "t1",
                "data": {
                    "id": "c1", "author": "a", "body": "top", "score": 1, "created_utc": 0.0,
                    "replies": { "data": { "children": [{
                        "kind": "t1",
                        "data": { "id": "c2", "author": "b", "body": "nested", "score": 1, "created_utc": 0.0, "replies": "" }
                    }] } }
                }
            }] } }
        ]);

        let thread = parse_thread(&data, 0).unwrap();
        assert_eq!(thread.comments.len(), 1);
        assert_eq!(thread.comments[0].id, "c1");
    }

    #[test]
    fn test_parse_comments_listing() {
        let data = serde_json::json!({
            "data": {
                "children": [{
                    "kind": "t1",
                    "data": { "id": "uc1", "author": "user1", "body": "My comment", "score": 7, "created_utc": 1700000000.0 }
                }]
            }
        });
        let resp = parse_comments_listing(&data, 25);
        assert_eq!(resp.comments.len(), 1);
        assert_eq!(resp.comments[0].id, "uc1");
        assert_eq!(resp.comments[0].body, "My comment");
    }

    #[test]
    fn test_parse_listing_extracts_after() {
        let data = serde_json::json!({
            "data": {
                "after": "t3_xyz789",
                "children": [
                    { "kind": "t3", "data": { "id": "a", "title": "A", "created_utc": 0.0 } },
                ]
            }
        });
        let resp = parse_listing(&data, 25);
        assert_eq!(resp.posts.len(), 1);
        assert_eq!(resp.after.as_deref(), Some("t3_xyz789"));
    }

    #[test]
    fn test_parse_listing_after_null() {
        let data = serde_json::json!({
            "data": {
                "after": null,
                "children": [
                    { "kind": "t3", "data": { "id": "a", "title": "A", "created_utc": 0.0 } },
                ]
            }
        });
        let resp = parse_listing(&data, 25);
        assert!(resp.after.is_none());
    }

    #[test]
    fn test_parse_thread_invalid() {
        let data = serde_json::json!({ "error": 404 });
        assert!(parse_thread(&data, 5).is_none());
    }
}
