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

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct IssueAddLabelRequest {
    pub label_id: i64,
}

#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct CreateLabelRequest {
    pub name: String,
    pub color: String,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct IssueLabelResponse {
    pub issue: Uuid,
    pub label_id: i64,
    pub label_name: Option<String>,
    pub label_color: Option<String>,
    pub relation_at: chrono::DateTime<Utc>,
}

#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct LabelResponse {
    pub id: i64,
    pub project: Uuid,
    pub name: String,
    pub color: String,
}

impl From<label::Model> for LabelResponse {
    fn from(l: label::Model) -> Self {
        Self {
            id: l.id,
            project: l.project,
            name: l.name,
            color: l.color,
        }
    }
}

impl AppService {
    /// List all labels for a project.
    pub async fn label_list(
        &self,
        project_name: String,
        ctx: &Session,
    ) -> Result<Vec<LabelResponse>, 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 labels = label::Entity::find()
            .filter(label::Column::Project.eq(project.id))
            .order_by_asc(label::Column::Name)
            .all(&self.db)
            .await?;

        Ok(labels.into_iter().map(LabelResponse::from).collect())
    }

    pub async fn label_create(
        &self,
        project_name: String,
        request: CreateLabelRequest,
        ctx: &Session,
    ) -> Result<LabelResponse, 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)?;

        // Check for duplicate label name in project
        let existing = label::Entity::find()
            .filter(label::Column::Project.eq(project.id))
            .filter(label::Column::Name.eq(&request.name))
            .one(&self.db)
            .await?;
        if existing.is_some() {
            return Err(AppError::BadRequest(
                "Label with this name already exists".to_string(),
            ));
        }

        let active = label::ActiveModel {
            id: Set(0), // auto-increment
            project: Set(project.id),
            name: Set(request.name),
            color: Set(request.color),
            ..Default::default()
        };
        let model = active.insert(&self.db).await?;

        Ok(LabelResponse::from(model))
    }

    pub async fn label_delete(
        &self,
        project_name: String,
        label_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 _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 lbl = label::Entity::find_by_id(label_id)
            .filter(label::Column::Project.eq(project.id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;

        // Cascade delete issue-label relations
        issue_label::Entity::delete_many()
            .filter(issue_label::Column::Label.eq(lbl.id))
            .exec(&self.db)
            .await?;

        label::Entity::delete_by_id(lbl.id).exec(&self.db).await?;

        Ok(())
    }

    /// List labels on an issue.
    pub async fn issue_label_list(
        &self,
        project_name: String,
        issue_number: i64,
        ctx: &Session,
    ) -> Result<Vec<IssueLabelResponse>, 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 issue_labels = issue_label::Entity::find()
            .filter(issue_label::Column::Issue.eq(issue.id))
            .all(&self.db)
            .await?;

        let label_ids: Vec<i64> = issue_labels.iter().map(|l| l.label).collect();
        let labels = if label_ids.is_empty() {
            vec![]
        } else {
            label::Entity::find()
                .filter(label::Column::Id.is_in(label_ids))
                .all(&self.db)
                .await?
        };

        let responses: Vec<IssueLabelResponse> = issue_labels
            .into_iter()
            .map(|il| {
                let lbl = labels.iter().find(|l| l.id == il.label);
                IssueLabelResponse {
                    issue: il.issue,
                    label_id: il.label,
                    label_name: lbl.map(|l| l.name.clone()),
                    label_color: lbl.map(|l| l.color.clone()),
                    relation_at: il.relation_at,
                }
            })
            .collect();

        Ok(responses)
    }

    /// Add a label to an issue.
    pub async fn issue_label_add(
        &self,
        project_name: String,
        issue_number: i64,
        request: IssueAddLabelRequest,
        ctx: &Session,
    ) -> Result<IssueLabelResponse, 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 lbl = label::Entity::find_by_id(request.label_id)
            .filter(label::Column::Project.eq(project.id))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;

        // Check if already linked
        let existing = issue_label::Entity::find()
            .filter(issue_label::Column::Issue.eq(issue.id))
            .filter(issue_label::Column::Label.eq(request.label_id))
            .one(&self.db)
            .await?;
        if existing.is_some() {
            return Err(AppError::BadRequest("Label already applied".to_string()));
        }

        let now = Utc::now();
        let active = issue_label::ActiveModel {
            issue: Set(issue.id),
            label: Set(lbl.id),
            relation_at: Set(now),
            ..Default::default()
        };
        let model = active.insert(&self.db).await?;

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

        let response = Ok(IssueLabelResponse {
            issue: model.issue,
            label_id: model.label,
            label_name: Some(lbl.name.clone()),
            label_color: Some(lbl.color.clone()),
            relation_at: model.relation_at,
        });

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                ActivityLogParams {
                    event_type: "issue_label_add".to_string(),
                    title: format!(
                        "{} added label '{}' to issue #{}",
                        user_uid,
                        lbl.name.clone(),
                        issue_number
                    ),
                    repo_id: None,
                    content: None,
                    event_id: Some(model.issue),
                    event_sub_id: Some(issue_number),
                    metadata: Some(serde_json::json!({
                        "label_id": lbl.id,
                        "label_name": lbl.name.clone(),
                        "label_color": lbl.color.clone(),
                    })),
                    is_private: false,
                },
            )
            .await;

        response
    }

    /// Remove a label from an issue.
    pub async fn issue_label_remove(
        &self,
        project_name: String,
        issue_number: i64,
        label_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 _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 issue_id = issue.id;
        let lbl = label::Entity::find_by_id(label_id)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;
        let label_name = lbl.name.clone();

        issue_label::Entity::delete_many()
            .filter(issue_label::Column::Issue.eq(issue.id))
            .filter(issue_label::Column::Label.eq(label_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_label_remove".to_string(),
                    title: format!(
                        "{} removed label '{}' from issue #{}",
                        user_uid, label_name, issue_number
                    ),
                    repo_id: None,
                    content: None,
                    event_id: Some(issue_id),
                    event_sub_id: Some(issue_number),
                    metadata: Some(serde_json::json!({
                        "label_id": label_id,
                        "label_name": label_name,
                    })),
                    is_private: false,
                },
            )
            .await;
        Ok(())
    }
}
