use crate::AppService;
use crate::error::AppError;
use chrono::{DateTime, Utc};
use models::projects::{project, project_follow};
use models::repos::{repo, repo_star};
use models::users::user;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RepoStarItem {
    pub uid: Uuid,
    pub repo_name: String,
    pub owner: String,
    pub description: Option<String>,
    pub is_private: bool,
    pub default_branch: String,
    pub starred_at: DateTime<Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct ProjectFollowItem {
    pub uid: Uuid,
    pub name: String,
    pub display_name: String,
    pub description: Option<String>,
    pub is_public: bool,
    pub followed_at: DateTime<Utc>,
}

#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct UserStarsResponse {
    pub repos: Vec<RepoStarItem>,
    pub projects: Vec<ProjectFollowItem>,
    pub total: u64,
}

impl AppService {
    pub async fn get_user_stars(
        &self,
        context: Session,
        username: String,
    ) -> Result<UserStarsResponse, AppError> {
        let target_user = user::Entity::find()
            .filter(user::Column::Username.eq(&username))
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;

        let current_uid = context.user();
        let is_owner = current_uid
            .map(|uid| uid == target_user.uid)
            .unwrap_or(false);

        // Repo stars
        let stars = repo_star::Entity::find()
            .filter(repo_star::Column::User.eq(target_user.uid))
            .order_by_desc(repo_star::Column::CreatedAt)
            .all(&self.db)
            .await?;

        let repo_ids: Vec<Uuid> = stars.iter().map(|s| s.repo).collect();

        let repos: std::collections::HashMap<Uuid, repo::Model> = repo::Entity::find()
            .filter(repo::Column::Id.is_in(repo_ids.clone()))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|r| (r.id, r))
            .collect();

        let project_ids: Vec<Uuid> = repos.values().map(|r| r.project).collect();
        let projects_map: std::collections::HashMap<Uuid, project::Model> = project::Entity::find()
            .filter(project::Column::Id.is_in(project_ids))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|p| (p.id, p))
            .collect();

        // Build repo items with privacy filter
        let mut repo_items: Vec<RepoStarItem> = Vec::new();
        for star in &stars {
            if let Some(r) = repos.get(&star.repo) {
                // Privacy: non-owners can only see public repos
                if !is_owner && r.is_private {
                    continue;
                }
                let owner_username = if let Some(p) = projects_map.get(&r.project) {
                    p.name.clone()
                } else {
                    String::new()
                };
                repo_items.push(RepoStarItem {
                    uid: r.id,
                    repo_name: r.repo_name.clone(),
                    owner: owner_username,
                    description: r.description.clone(),
                    is_private: r.is_private,
                    default_branch: r.default_branch.clone(),
                    starred_at: star.created_at,
                });
            }
        }

        // Project follows
        let follows = project_follow::Entity::find()
            .filter(project_follow::Column::User.eq(target_user.uid))
            .order_by_desc(project_follow::Column::CreatedAt)
            .all(&self.db)
            .await?;

        let followed_project_ids: Vec<Uuid> = follows.iter().map(|f| f.project).collect();

        let followed_projects: std::collections::HashMap<Uuid, project::Model> = project::Entity::find()
            .filter(project::Column::Id.is_in(followed_project_ids))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|p| (p.id, p))
            .collect();

        let mut project_items: Vec<ProjectFollowItem> = Vec::new();
        for follow in &follows {
            if let Some(p) = followed_projects.get(&follow.project) {
                // Privacy: non-owners can only see public projects
                if !is_owner && !p.is_public {
                    continue;
                }
                project_items.push(ProjectFollowItem {
                    uid: p.id,
                    name: p.name.clone(),
                    display_name: p.display_name.clone(),
                    description: p.description.clone(),
                    is_public: p.is_public,
                    followed_at: follow.created_at,
                });
            }
        }

        let total = repo_items.len() as u64 + project_items.len() as u64;

        Ok(UserStarsResponse {
            repos: repo_items,
            projects: project_items,
            total,
        })
    }
}
