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

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct IssueAssignUserRequest {
    pub user_id: Uuid,
}

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

impl AppService {
    /// List assignees for an issue.
    pub async fn issue_assignee_list(
        &self,
        project_name: String,
        issue_number: i64,
        ctx: &Session,
    ) -> Result<Vec<IssueAssigneeResponse>, 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 assignees = issue_assignee::Entity::find()
            .filter(issue_assignee::Column::Issue.eq(issue.id))
            .all(&self.db)
            .await?;

        let user_ids: Vec<Uuid> = assignees.iter().map(|a| a.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<IssueAssigneeResponse> = assignees
            .into_iter()
            .filter_map(|a| {
                let username = users.iter().find(|u| u.uid == a.user)?.username.clone();
                Some(IssueAssigneeResponse {
                    issue: a.issue,
                    user_id: a.user,
                    username,
                    assigned_at: a.assigned_at,
                })
            })
            .collect();

        Ok(responses)
    }

    /// Assign a user to an issue.
    pub async fn issue_assignee_add(
        &self,
        project_name: String,
        issue_number: i64,
        request: IssueAssignUserRequest,
        ctx: &Session,
    ) -> Result<IssueAssigneeResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).await?;

        // Must be project member
        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()))?;

        // Check if assignee is also a member
        let assignee_member = project_members::Entity::find()
            .filter(project_members::Column::Project.eq(project.id))
            .filter(project_members::Column::User.eq(request.user_id))
            .one(&self.db)
            .await?;
        if assignee_member.is_none() {
            return Err(AppError::NotFound(
                "User is not a project member".to_string(),
            ));
        }

        let now = Utc::now();
        let active = issue_assignee::ActiveModel {
            issue: Set(issue.id),
            user: Set(request.user_id),
            assigned_at: Set(now),
            ..Default::default()
        };
        let model = active.insert(&self.db).await?;

        self.invalidate_issue_cache(project.id, issue_number).await;

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

        let response = Ok(IssueAssigneeResponse {
            issue: model.issue,
            user_id: model.user,
            username: username.clone(),
            assigned_at: model.assigned_at,
        });

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

        response
    }

    /// Remove an assignee from an issue.
    pub async fn issue_assignee_remove(
        &self,
        project_name: String,
        issue_number: i64,
        assignee_id: Uuid,
        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()))?;

        // Allow: the assignee themselves, or any admin/owner, or the issue author
        let role = member.scope_role().map_err(|_| AppError::RoleParseError)?;
        let is_self = assignee_id == user_uid;
        let is_author = issue.author == user_uid;
        let is_admin = role == models::projects::MemberRole::Admin
            || role == models::projects::MemberRole::Owner;

        if !is_self && !is_author && !is_admin {
            return Err(AppError::NoPower);
        }

        issue_assignee::Entity::delete_many()
            .filter(issue_assignee::Column::Issue.eq(issue.id))
            .filter(issue_assignee::Column::User.eq(assignee_id))
            .exec(&self.db)
            .await?;

        self.invalidate_issue_cache(project.id, issue_number).await;

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