use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::repos::repo as repo_model;
use models::repos::{RepoStar, repo_star};
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StarCountResponse {
    pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StarUserInfo {
    pub uid: String,
    pub username: String,
    pub avatar_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct StarUserListResponse {
    pub users: Vec<StarUserInfo>,
}
impl AppService {
    pub async fn git_star(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo = self
            .utils_find_repo(namespace, repo_name.clone(), ctx)
            .await?;
        let existing = RepoStar::find()
            .filter(repo_star::Column::User.eq(user_uid))
            .filter(repo_star::Column::Repo.eq(repo.id))
            .one(&self.db)
            .await?;
        if existing.is_some() {
            return Err(AppError::InternalServerError("already starred".to_string()));
        }
        RepoStar::insert(repo_star::ActiveModel {
            id: Default::default(),
            repo: Set(repo.id),
            user: Set(user_uid),
            created_at: Set(Utc::now()),
        })
        .exec(&self.db)
        .await?;

        // Log activity: need to look up project_id from repo
        let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await {
            Ok(Some(r)) => r.project,
            Ok(None) => Uuid::nil(),
            Err(e) => {
                slog::warn!(
                    self.logs,
                    "failed to look up project_id for activity log: {}",
                    e
                );
                Uuid::nil()
            }
        };
        let _ = self
            .project_log_activity(
                project_id,
                Some(repo.id),
                user_uid,
                super::super::project::activity::ActivityLogParams {
                    event_type: "repo_star".to_string(),
                    title: format!("{} starred repository '{}'", user_uid, repo_name),
                    repo_id: Some(repo.id),
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "repo_name": repo_name,
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }

    pub async fn git_unstar(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo = self
            .utils_find_repo(namespace, repo_name.clone(), ctx)
            .await?;
        let deleted = RepoStar::delete_many()
            .filter(repo_star::Column::User.eq(user_uid))
            .filter(repo_star::Column::Repo.eq(repo.id))
            .exec(&self.db)
            .await?;
        if deleted.rows_affected == 0 {
            return Err(AppError::InternalServerError("not starred".to_string()));
        }
        let project_id = match repo_model::Entity::find_by_id(repo.id).one(&self.db).await {
            Ok(Some(r)) => r.project,
            Ok(None) => Uuid::nil(),
            Err(e) => {
                slog::warn!(
                    self.logs,
                    "failed to look up project_id for activity log: {}",
                    e
                );
                Uuid::nil()
            }
        };
        let _ = self
            .project_log_activity(
                project_id,
                Some(repo.id),
                user_uid,
                super::super::project::activity::ActivityLogParams {
                    event_type: "repo_unstar".to_string(),
                    title: format!("{} unstarred repository '{}'", user_uid, repo_name),
                    repo_id: Some(repo.id),
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({"repo_name": repo_name})),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }

    pub async fn git_is_starred(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<bool, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let repo = self
            .utils_find_repo(namespace, repo_name.clone(), ctx)
            .await?;
        let existing = RepoStar::find()
            .filter(repo_star::Column::User.eq(user_uid))
            .filter(repo_star::Column::Repo.eq(repo.id))
            .one(&self.db)
            .await?;
        Ok(existing.is_some())
    }

    pub async fn git_star_count(
        &self,
        namespace: String,
        repo_name: String,
        ctx: &Session,
    ) -> Result<StarCountResponse, AppError> {
        let repo = self
            .utils_find_repo(namespace, repo_name.clone(), ctx)
            .await?;
        let count = RepoStar::find()
            .filter(repo_star::Column::Repo.eq(repo.id))
            .count(&self.db)
            .await?;
        Ok(StarCountResponse {
            count: count as i64,
        })
    }

    pub async fn git_star_user_list(
        &self,
        namespace: String,
        repo_name: String,
        pager: crate::Pager,
        ctx: &Session,
    ) -> Result<StarUserListResponse, AppError> {
        let repo = self
            .utils_find_repo(namespace, repo_name.clone(), ctx)
            .await?;
        let page = std::cmp::Ord::max(pager.page, 1);
        let par_page = std::cmp::Ord::min(std::cmp::Ord::max(pager.par_page, 1), 1000);
        let offset_val = (page - 1).saturating_mul(par_page);
        let offset = offset_val as u64;
        let stars = RepoStar::find()
            .filter(repo_star::Column::Repo.eq(repo.id))
            .order_by_desc(repo_star::Column::CreatedAt)
            .limit(par_page as u64)
            .offset(offset)
            .all(&self.db)
            .await?;
        let user_uids: Vec<uuid::Uuid> = stars.into_iter().map(|s| s.user).collect();
        if user_uids.is_empty() {
            return Ok(StarUserListResponse { users: vec![] });
        }
        let users = models::users::user::Entity::find()
            .filter(models::users::user::Column::Uid.is_in(user_uids))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|u| StarUserInfo {
                uid: u.uid.to_string(),
                username: u.username,
                avatar_url: u.avatar_url.unwrap_or_default(),
            })
            .collect();
        Ok(StarUserListResponse { users })
    }
}
