use crate::AppService;
use crate::error::AppError;
use chrono::Utc;
use models::projects::{MemberRole, project_audit_log, project_label};
use models::system::label;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct CreateLabelParams {
    pub name: String,
    pub color: String,
    pub description: Option<String>,
}

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct UpdateLabelParams {
    pub name: Option<String>,
    pub color: Option<String>,
    pub description: Option<String>,
}

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct LabelResponse {
    pub id: i64,
    pub project_uid: Uuid,
    pub name: String,
    pub color: String,
    pub description: Option<String>,
    pub created_at: chrono::DateTime<Utc>,
}

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct LabelListResponse {
    pub labels: Vec<LabelResponse>,
    pub total: usize,
}

impl AppService {
    pub async fn project_create_label(
        &self,
        project_name: String,
        params: CreateLabelParams,
        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?;

        self.utils_check_project_permission(
            &project.id,
            user_uid,
            &[MemberRole::Admin, MemberRole::Owner],
        )
        .await?;

        // Create the label in system::label table
        let label_model = label::ActiveModel {
            project: Set(project.id),
            name: Set(params.name.clone()),
            color: Set(params.color.clone()),
            ..Default::default()
        };

        let created_system_label = label_model.insert(&self.db).await?;

        // Create the project-label relation
        let project_label = project_label::ActiveModel {
            project: Set(project.id),
            label: Set(created_system_label.id),
            relation_at: Set(Utc::now()),
            ..Default::default()
        };

        let created_project_label = project_label.insert(&self.db).await?;

        let _ = self
            .project_log_activity(
                project.id,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "label_create".to_string(),
                    title: format!("{} created label '{}'", user_uid, params.name),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(created_project_label.id),
                    metadata: Some(serde_json::json!({
                        "label_id": created_project_label.id,
                        "label_name": params.name,
                        "color": params.color,
                    })),
                    is_private: false,
                },
            )
            .await;

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

        Ok(LabelResponse {
            id: created_project_label.id,
            project_uid: project.id,
            name: params.name,
            color: params.color,
            description: params.description,
            created_at: created_project_label.relation_at,
        })
    }

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

        // Find all project-label relations
        let project_label_relations = project_label::Entity::find()
            .filter(project_label::Column::Project.eq(project.id))
            .all(&self.db)
            .await?;

        if project_label_relations.is_empty() {
            return Ok(LabelListResponse {
                labels: vec![],
                total: 0,
            });
        }

        // Get all label IDs
        let label_ids: Vec<i64> = project_label_relations.iter().map(|r| r.label).collect();

        // Fetch label details
        let labels = label::Entity::find()
            .filter(label::Column::Id.is_in(label_ids))
            .all(&self.db)
            .await?;

        let total = labels.len();
        let labels: Vec<LabelResponse> = project_label_relations
            .into_iter()
            .filter_map(|relation| {
                labels
                    .iter()
                    .find(|l| l.id == relation.label)
                    .map(|l| LabelResponse {
                        id: relation.id,
                        project_uid: relation.project,
                        name: l.name.clone(),
                        color: l.color.clone(),
                        description: None, // system::label doesn't have description
                        created_at: relation.relation_at,
                    })
            })
            .collect();

        Ok(LabelListResponse { labels, total })
    }

    pub async fn project_get_label(&self, label_id: i64) -> Result<LabelResponse, AppError> {
        let project_label = project_label::Entity::find_by_id(label_id)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;

        let system_label = label::Entity::find_by_id(project_label.label)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;

        Ok(LabelResponse {
            id: project_label.id,
            project_uid: project_label.project,
            name: system_label.name,
            color: system_label.color,
            description: None,
            created_at: project_label.relation_at,
        })
    }

    pub async fn project_update_label(
        &self,
        label_id: i64,
        params: UpdateLabelParams,
        ctx: &Session,
    ) -> Result<LabelResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

        let project_label = project_label::Entity::find_by_id(label_id)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;

        self.utils_check_project_permission(
            &project_label.project,
            user_uid,
            &[MemberRole::Admin, MemberRole::Owner],
        )
        .await?;

        let system_label = label::Entity::find_by_id(project_label.label)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;

        let mut active_label: label::ActiveModel = system_label.into();

        let updated_name = params.name.is_some();
        let updated_color = params.color.is_some();
        let updated_description = params.description.is_some();

        if let Some(name) = params.name {
            active_label.name = Set(name);
        }
        if let Some(color) = params.color {
            active_label.color = Set(color);
        }

        let updated_system_label = active_label.update(&self.db).await?;

        let _ = self
            .project_log_activity(
                project_label.project,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "label_update".to_string(),
                    title: format!("{} updated label '{}'", user_uid, updated_system_label.name),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(label_id),
                    metadata: Some(serde_json::json!({
                        "label_id": label_id,
                        "updated_fields": {
                            "name": updated_name,
                            "color": updated_color,
                            "description": updated_description,
                        }
                    })),
                    is_private: false,
                },
            )
            .await;

        let log = project_audit_log::ActiveModel {
            project: Set(project_label.project),
            actor: Set(user_uid),
            action: Set("label_update".to_string()),
            details: Set(Some(serde_json::json!({
                "label_id": label_id,
                "updated_fields": {
                    "name": updated_name,
                    "color": updated_color,
                    "description": updated_description,
                }
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&self.db).await?;

        Ok(LabelResponse {
            id: project_label.id,
            project_uid: project_label.project,
            name: updated_system_label.name,
            color: updated_system_label.color,
            description: params.description,
            created_at: project_label.relation_at,
        })
    }

    pub async fn project_delete_label(&self, label_id: i64, ctx: &Session) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

        let project_label = project_label::Entity::find_by_id(label_id)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Label not found".to_string()))?;

        self.utils_check_project_permission(
            &project_label.project,
            user_uid,
            &[MemberRole::Admin, MemberRole::Owner],
        )
        .await?;

        let system_label = label::Entity::find_by_id(project_label.label)
            .one(&self.db)
            .await?;

        let deleted_label_name = system_label
            .as_ref()
            .map(|l| l.name.clone())
            .unwrap_or_default();
        let _ = self
            .project_log_activity(
                project_label.project,
                None,
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "label_delete".to_string(),
                    title: format!("{} deleted label '{}'", user_uid, deleted_label_name),
                    repo_id: None,
                    content: None,
                    event_id: None,
                    event_sub_id: Some(label_id),
                    metadata: Some(serde_json::json!({
                        "label_id": label_id,
                        "label_name": deleted_label_name,
                    })),
                    is_private: false,
                },
            )
            .await;

        let log = project_audit_log::ActiveModel {
            project: Set(project_label.project),
            actor: Set(user_uid),
            action: Set("label_delete".to_string()),
            details: Set(Some(serde_json::json!({
                "label_id": label_id,
                "label_name": system_label.as_ref().map(|l| l.name.clone()),
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&self.db).await?;

        project_label::Entity::delete_by_id(label_id)
            .exec(&self.db)
            .await?;

        // Also delete the system label if it exists
        if let Some(sl) = system_label {
            label::Entity::delete_by_id(sl.id).exec(&self.db).await?;
        }

        Ok(())
    }
}
