use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::projects::{ProjectWatch, project_audit_log, project_watch};
use models::users::user_email;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, utoipa::ToSchema)]
pub struct WatchUserInfo {
    pub uid: Uuid,
    pub username: String,
    pub avatar_url: String,
}

impl AppService {
    pub async fn project_watch(&self, ctx: &Session, project_name: String) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let watch_exists = ProjectWatch::find()
            .filter(project_watch::Column::User.eq(user_uid))
            .filter(project_watch::Column::Project.eq(project.id))
            .one(&self.db)
            .await?;

        if watch_exists.is_some() {
            return Err(AppError::BadRequest(
                "Already watching this project".to_string(),
            ));
        }

        ProjectWatch::insert(project_watch::ActiveModel {
            id: Default::default(),
            project: Set(project.id),
            user: Set(user_uid),
            notifications_enabled: Set(true),
            created_at: Set(Utc::now()),
            updated_at: Set(Utc::now()),
        })
        .exec(&self.db)
        .await?;

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "project_watch".to_string(),
                    title: format!("{} started watching the project", user_uid),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "project_name": project.name.clone(),
                    })),
                    is_private: false,
                },
            )
            .await;

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("project_watch".to_string()),
            details: Set(Some(serde_json::json!({
                "project_name": project.name.clone(),
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&self.db).await?;

        Ok(())
    }

    pub async fn project_unwatch(
        &self,
        ctx: &Session,
        project_name: String,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let watch_exists = ProjectWatch::find()
            .filter(project_watch::Column::User.eq(user_uid))
            .filter(project_watch::Column::Project.eq(project.id))
            .one(&self.db)
            .await?;

        if watch_exists.is_none() {
            return Err(AppError::NotFound("Not watching this project".to_string()));
        }

        ProjectWatch::delete_many()
            .filter(project_watch::Column::User.eq(user_uid))
            .filter(project_watch::Column::Project.eq(project.id))
            .exec(&self.db)
            .await?;

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "project_unwatch".to_string(),
                    title: format!("{} stopped watching the project", user_uid),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "project_name": project.name.clone(),
                    })),
                    is_private: false,
                },
            )
            .await;

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("project_unwatch".to_string()),
            details: Set(Some(serde_json::json!({
                "project_name": project.name.clone(),
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&self.db).await?;

        Ok(())
    }

    pub async fn project_is_watch(
        &self,
        ctx: &Session,
        project_name: String,
    ) -> Result<bool, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let watch_exists = ProjectWatch::find()
            .filter(project_watch::Column::User.eq(user_uid))
            .filter(project_watch::Column::Project.eq(project.id))
            .one(&self.db)
            .await?;

        Ok(watch_exists.is_some())
    }

    pub async fn project_watches(&self, project_name: String) -> Result<u64, AppError> {
        let project = self.utils_find_project_by_name(project_name).await?;

        let watches = ProjectWatch::find()
            .filter(project_watch::Column::Project.eq(project.id))
            .count(&self.db)
            .await?;

        Ok(watches)
    }

    pub async fn project_watch_user_list(
        &self,
        project_name: String,
        pager: crate::Pager,
    ) -> Result<Vec<WatchUserInfo>, AppError> {
        let project = self.utils_find_project_by_name(project_name).await?;

        let watches = ProjectWatch::find()
            .filter(project_watch::Column::Project.eq(project.id))
            .order_by_desc(project_watch::Column::CreatedAt)
            .limit(pager.par_page as u64)
            .offset(((pager.page - 1) * pager.par_page) as u64)
            .all(&self.db)
            .await?;

        let user_uids: Vec<Uuid> = watches.into_iter().map(|watch| watch.user).collect();

        if user_uids.is_empty() {
            return Ok(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,
                username: u.username,
                avatar_url: u.avatar_url.unwrap_or_default(),
            })
            .collect();

        Ok(users)
    }

    pub async fn project_watch_user_emails(
        &self,
        project_uid: Uuid,
        notify: bool,
    ) -> Result<Vec<String>, AppError> {
        let watches = ProjectWatch::find()
            .filter(project_watch::Column::Project.eq(project_uid))
            .all(&self.db)
            .await?
            .into_iter()
            .filter(|x| x.notifications_enabled == notify)
            .collect::<Vec<_>>();

        let user_uids: Vec<Uuid> = watches.into_iter().map(|watch| watch.user).collect();

        if user_uids.is_empty() {
            return Ok(vec![]);
        }

        let emails = user_email::Entity::find()
            .filter(user_email::Column::User.is_in(user_uids))
            .all(&self.db)
            .await?
            .into_iter()
            .map(|u| u.email)
            .collect();

        Ok(emails)
    }
}
