use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::repos::repo_watch;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;

#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WatchCountResponse {
    pub count: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WatchUserInfo {
    pub uid: String,
    pub username: String,
    pub avatar_url: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WatchUserListResponse {
    pub users: Vec<WatchUserInfo>,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct GitWatchRequest {
    #[serde(default = "default_show_dashboard")]
    pub show_dashboard: bool,
    #[serde(default)]
    pub notify_email: bool,
}
fn default_show_dashboard() -> bool {
    true
}
use models::repos::RepoWatch;
use models::repos::repo as repo_model;
use uuid::Uuid;
impl AppService {
    pub async fn git_watch(
        &self,
        namespace: String,
        repo_name: String,
        request: GitWatchRequest,
        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: Option<repo_watch::Model> = RepoWatch::find()
            .filter(repo_watch::Column::User.eq(user_uid))
            .filter(repo_watch::Column::Repo.eq(repo.id))
            .one(&self.db)
            .await?;
        if existing.is_some() {
            return Err(AppError::Conflict("already watching".to_string()));
        }
        RepoWatch::insert(repo_watch::ActiveModel {
            id: Default::default(),
            user: Set(user_uid),
            repo: Set(repo.id),
            show_dashboard: Set(request.show_dashboard),
            notify_email: Set(request.notify_email),
            created_at: Set(Utc::now()),
            updated_at: Set(Utc::now()),
        })
        .exec(&self.db)
        .await?;
        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) => {
                tracing::warn!(error = ?e, "failed to look up project_id for activity log");
                Uuid::nil()
            }
        };
        let _ = self
            .project_log_activity(
                project_id,
                Some(repo.id),
                user_uid,
                super::super::project::activity::ActivityLogParams {
                    event_type: "repo_watch".to_string(),
                    title: format!("{} started watching 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_unwatch(
        &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 = RepoWatch::delete_many()
            .filter(repo_watch::Column::User.eq(user_uid))
            .filter(repo_watch::Column::Repo.eq(repo.id))
            .exec(&self.db)
            .await?;
        if deleted.rows_affected == 0 {
            return Err(AppError::NotFound("not watching".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) => {
                tracing::warn!(error = ?e, "failed to look up project_id for activity log");
                Uuid::nil()
            }
        };
        let _ = self
            .project_log_activity(
                project_id,
                Some(repo.id),
                user_uid,
                super::super::project::activity::ActivityLogParams {
                    event_type: "repo_unwatch".to_string(),
                    title: format!("{} stopped watching 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_watched(
        &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, ctx).await?;
        let existing: Option<repo_watch::Model> = RepoWatch::find()
            .filter(repo_watch::Column::User.eq(user_uid))
            .filter(repo_watch::Column::Repo.eq(repo.id))
            .one(&self.db)
            .await?;
        Ok(existing.is_some())
    }

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

    pub async fn git_watch_user_list(
        &self,
        namespace: String,
        repo_name: String,
        pager: crate::Pager,
        ctx: &Session,
    ) -> Result<WatchUserListResponse, AppError> {
        let repo = self.utils_find_repo(namespace, repo_name, 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 watches: Vec<repo_watch::Model> = RepoWatch::find()
            .filter(repo_watch::Column::Repo.eq(repo.id))
            .order_by_desc(repo_watch::Column::CreatedAt)
            .limit(par_page as u64)
            .offset(offset)
            .all(&self.db)
            .await?;
        let user_uids: Vec<uuid::Uuid> = watches.into_iter().map(|w| w.user).collect();
        if user_uids.is_empty() {
            return Ok(WatchUserListResponse { 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| WatchUserInfo {
                uid: u.uid.to_string(),
                username: u.username,
                avatar_url: u.avatar_url.unwrap_or_default(),
            })
            .collect();
        Ok(WatchUserListResponse { users })
    }
}
