use crate::AppService;
use crate::error::AppError;
use crate::project::activity::ActivityLogParams;
use chrono::Utc;
use models::issues::{issue, issue_comment};
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 IssueCommentCreateRequest {
    pub body: String,
}

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct IssueCommentUpdateRequest {
    pub body: String,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct IssueCommentResponse {
    pub id: i64,
    pub issue: Uuid,
    pub author: Uuid,
    pub author_username: String,
    pub body: String,
    pub created_at: chrono::DateTime<Utc>,
    pub updated_at: chrono::DateTime<Utc>,
}

impl From<issue_comment::Model> for IssueCommentResponse {
    fn from(c: issue_comment::Model) -> Self {
        Self {
            id: c.id,
            issue: c.issue,
            author: c.author,
            author_username: String::new(),
            body: c.body,
            created_at: c.created_at,
            updated_at: c.updated_at,
        }
    }
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct IssueCommentListResponse {
    pub comments: Vec<IssueCommentResponse>,
    pub total: i64,
    pub page: i64,
    pub per_page: i64,
}

impl AppService {
    /// List comments on an issue.
    pub async fn issue_comment_list(
        &self,
        project_name: String,
        issue_number: i64,
        page: Option<i64>,
        per_page: Option<i64>,
        ctx: &Session,
    ) -> Result<IssueCommentListResponse, 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 page = page.unwrap_or(1);
        let per_page = per_page.unwrap_or(20);
        let offset = (page - 1) * per_page;

        let total: u64 = issue_comment::Entity::find()
            .filter(issue_comment::Column::Issue.eq(issue.id))
            .count(&self.db)
            .await?;

        let comments = issue_comment::Entity::find()
            .filter(issue_comment::Column::Issue.eq(issue.id))
            .order_by_asc(issue_comment::Column::CreatedAt)
            .offset(offset as u64)
            .limit(per_page as u64)
            .all(&self.db)
            .await?;

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

        let responses: Vec<IssueCommentResponse> = comments
            .into_iter()
            .map(|c| {
                let username = authors
                    .iter()
                    .find(|u| u.uid == c.author)
                    .map(|u| u.username.clone())
                    .unwrap_or_default();
                IssueCommentResponse {
                    author_username: username,
                    ..IssueCommentResponse::from(c)
                }
            })
            .collect();

        Ok(IssueCommentListResponse {
            comments: responses,
            total: total as i64,
            page,
            per_page,
        })
    }

    /// Get a single comment by ID.
    pub async fn issue_comment_get(
        &self,
        project_name: String,
        issue_number: i64,
        comment_id: i64,
        ctx: &Session,
    ) -> Result<IssueCommentResponse, 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 comment = issue_comment::Entity::find_by_id(comment_id)
            .filter(issue_comment::Column::Issue.eq(issue.id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Comment not found".to_string()))?;

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

        Ok(IssueCommentResponse {
            author_username: username,
            ..IssueCommentResponse::from(comment)
        })
    }

    /// Create a comment on an issue.
    pub async fn issue_comment_create(
        &self,
        project_name: String,
        issue_number: i64,
        request: IssueCommentCreateRequest,
        ctx: &Session,
    ) -> Result<IssueCommentResponse, 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?;
        if member.is_none() {
            return Err(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();
        let active = issue_comment::ActiveModel {
            issue: Set(issue.id),
            author: Set(user_uid),
            body: Set(request.body),
            created_at: Set(now),
            updated_at: Set(now),
            ..Default::default()
        };
        let model = active.insert(&self.db).await?;

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

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

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                crate::project::activity::ActivityLogParams {
                    event_type: "issue_comment".to_string(),
                    title: format!("{} commented on issue #{}", actor_username, issue_number),
                    repo_id: None,
                    content: Some(model.body.clone()),
                    event_id: None,
                    event_sub_id: Some(issue_number),
                    metadata: None,
                    is_private: false,
                },
            )
            .await;

        Ok(IssueCommentResponse {
            author_username: actor_username,
            ..IssueCommentResponse::from(model)
        })
    }

    /// Update a comment body.
    pub async fn issue_comment_update(
        &self,
        project_name: String,
        issue_number: i64,
        comment_id: i64,
        request: IssueCommentUpdateRequest,
        ctx: &Session,
    ) -> Result<IssueCommentResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self.utils_find_project_by_name(project_name).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 comment = issue_comment::Entity::find_by_id(comment_id)
            .filter(issue_comment::Column::Issue.eq(issue.id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Comment not found".to_string()))?;

        // Only author can edit their comment
        if comment.author != user_uid {
            return Err(AppError::NoPower);
        }

        let mut active: issue_comment::ActiveModel = comment.clone().into();
        active.body = Set(request.body);
        active.updated_at = Set(Utc::now());
        let model = active.update(&self.db).await?;

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

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                ActivityLogParams {
                    event_type: "issue_comment_update".to_string(),
                    title: format!("{} updated a comment on issue #{}", user_uid, issue_number),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(issue_number),
                    metadata: None,
                    is_private: false,
                },
            )
            .await;
        Ok(IssueCommentResponse {
            author_username: username,
            ..IssueCommentResponse::from(model)
        })
    }

    /// Delete a comment.
    pub async fn issue_comment_delete(
        &self,
        project_name: String,
        issue_number: i64,
        comment_id: 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 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 comment = issue_comment::Entity::find_by_id(comment_id)
            .filter(issue_comment::Column::Issue.eq(issue.id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Comment not found".to_string()))?;

        // Allow: author of comment, or issue author, or admin/owner
        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 role = member.scope_role().map_err(|_| AppError::RoleParseError)?;
        let is_comment_author = comment.author == user_uid;
        let is_issue_author = issue.author == user_uid;
        let is_admin = role == models::projects::MemberRole::Admin
            || role == models::projects::MemberRole::Owner;

        if !is_comment_author && !is_issue_author && !is_admin {
            return Err(AppError::NoPower);
        }

        issue_comment::Entity::delete_by_id(comment_id)
            .exec(&self.db)
            .await?;

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

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                ActivityLogParams {
                    event_type: "issue_comment_delete".to_string(),
                    title: format!("{} deleted a comment on issue #{}", user_uid, issue_number),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(issue_number),
                    metadata: None,
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }
}
