use crate::AppService;
use crate::error::AppError;
use crate::project::activity::ActivityLogParams;
use chrono::Utc;
use models::issues::{issue, issue_subscriber};
use models::projects::project_members;
use models::users::user;
use sea_orm::*;
use serde::Serialize;
use session::Session;
use utoipa::ToSchema;
use uuid::Uuid;

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct IssueSubscriberResponse {
    pub issue: Uuid,
    pub user_id: Uuid,
    pub username: String,
    pub subscribed: bool,
    pub created_at: chrono::DateTime<Utc>,
}

impl AppService {
    /// List subscribers for an issue.
    pub async fn issue_subscriber_list(
        &self,
        project_name: String,
        issue_number: i64,
        ctx: &Session,
    ) -> Result<Vec<IssueSubscriberResponse>, AppError> {
        let project = self.utils_find_project_by_name(project_name).await?;
        if let Some(uid) = ctx.user() {
            self.check_project_access(project.id, uid).await?;
        }

        let issue = issue::Entity::find()
            .filter(issue::Column::Project.eq(project.id))
            .filter(issue::Column::Number.eq(issue_number))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Issue not found".to_string()))?;

        let subscribers = issue_subscriber::Entity::find()
            .filter(issue_subscriber::Column::Issue.eq(issue.id))
            .filter(issue_subscriber::Column::Subscribed.eq(true))
            .all(&self.db)
            .await?;

        let user_ids: Vec<Uuid> = subscribers.iter().map(|s| s.user).collect();
        let users = if user_ids.is_empty() {
            vec![]
        } else {
            user::Entity::find()
                .filter(user::Column::Uid.is_in(user_ids))
                .all(&self.db)
                .await?
        };

        let responses: Vec<IssueSubscriberResponse> = subscribers
            .into_iter()
            .filter_map(|s| {
                let username = users.iter().find(|u| u.uid == s.user)?.username.clone();
                Some(IssueSubscriberResponse {
                    issue: s.issue,
                    user_id: s.user,
                    username,
                    subscribed: s.subscribed,
                    created_at: s.created_at,
                })
            })
            .collect();

        Ok(responses)
    }

    /// Subscribe the current user to an issue.
    pub async fn issue_subscribe(
        &self,
        project_name: String,
        issue_number: i64,
        ctx: &Session,
    ) -> Result<IssueSubscriberResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let _member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NoPower)?;

        let issue = issue::Entity::find()
            .filter(issue::Column::Project.eq(project.id))
            .filter(issue::Column::Number.eq(issue_number))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Issue not found".to_string()))?;

        let now = Utc::now();

        // Upsert: update existing record or create new
        let existing = issue_subscriber::Entity::find()
            .filter(issue_subscriber::Column::Issue.eq(issue.id))
            .filter(issue_subscriber::Column::User.eq(user_uid))
            .one(&self.db)
            .await?;

        let model = if let Some(existing) = existing {
            let mut active: issue_subscriber::ActiveModel = existing.into();
            active.subscribed = Set(true);
            active.update(&self.db).await?
        } else {
            let active = issue_subscriber::ActiveModel {
                issue: Set(issue.id),
                user: Set(user_uid),
                subscribed: Set(true),
                created_at: Set(now),
                ..Default::default()
            };
            active.insert(&self.db).await?
        };

        let username = user::Entity::find_by_id(user_uid)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .map(|u| u.username)
            .unwrap_or_default();

        let response = Ok(IssueSubscriberResponse {
            issue: model.issue,
            user_id: model.user,
            username: username.clone(),
            subscribed: model.subscribed,
            created_at: model.created_at,
        });

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                ActivityLogParams {
                    event_type: "issue_subscribe".to_string(),
                    title: format!("{} subscribed to issue #{}", username.clone(), issue_number),
                    repo_id: None,
                    content: None,
                    event_id: Some(model.issue),
                    event_sub_id: Some(issue_number),
                    metadata: None,
                    is_private: false,
                },
            )
            .await;

        response
    }

    /// Unsubscribe the current user from an issue.
    pub async fn issue_unsubscribe(
        &self,
        project_name: String,
        issue_number: i64,
        ctx: &Session,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        let _member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::NoPower)?;

        let issue = issue::Entity::find()
            .filter(issue::Column::Project.eq(project.id))
            .filter(issue::Column::Number.eq(issue_number))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Issue not found".to_string()))?;

        // Soft-delete: set subscribed = false
        let existing = issue_subscriber::Entity::find()
            .filter(issue_subscriber::Column::Issue.eq(issue.id))
            .filter(issue_subscriber::Column::User.eq(user_uid))
            .one(&self.db)
            .await?;

        if let Some(existing) = existing {
            let mut active: issue_subscriber::ActiveModel = existing.into();
            active.subscribed = Set(false);
            active.update(&self.db).await?;
        }

        let subscriber_username = user::Entity::find_by_id(user_uid)
            .one(&self.db)
            .await
            .ok()
            .flatten()
            .map(|u| u.username)
            .unwrap_or_else(|| user_uid.to_string());
        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                ActivityLogParams {
                    event_type: "issue_unsubscribe".to_string(),
                    title: format!(
                        "{} unsubscribed from issue #{}",
                        subscriber_username, issue_number
                    ),
                    repo_id: None,
                    content: None,
                    event_id: Some(issue.id),
                    event_sub_id: Some(issue_number),
                    metadata: None,
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }
}
