use crate::{AppService, error::AppError};
use chrono::Utc;
use models::projects::{MemberRole, project_audit_log, project_history_name};
use models::repos::{Repo, repo};
use models::users::user;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct ExchangeProjectVisibility {
    pub is_public: bool,
}

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

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct ExchangeProjectName {
    pub name: String,
}

impl AppService {
    pub async fn project_exchange_name(
        &self,
        ctx: &Session,
        project_name: String,
        params: ExchangeProjectName,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .await?;
        let oper = user::Entity::find()
            .filter(user::Column::Uid.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::UserNotFound)?;

        if let Ok(role) = self.utils_project_context_role(&ctx, project_name).await {
            if role != MemberRole::Owner {
                return Err(AppError::NoPower);
            }
        } else {
            return Err(AppError::NoPower);
        }

        let txn = self.db.begin().await?;

        let mut active = project.clone().into_active_model();
        active.name = Set(params.name.clone());
        active.update(&txn).await?;

        // Update repo names for all repos in this project
        let repos = Repo::find()
            .filter(repo::Column::Project.eq(project.id))
            .all(&txn)
            .await?;

        for r in repos {
            let repo_name = r.repo_name.clone();
            let mut ra: repo::ActiveModel = r.into();
            ra.repo_name = Set(format!("{}/{}", params.name.clone(), repo_name));
            ra.update(&txn).await?;
        }

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("exchange_name".to_string()),
            details: Set(Some(serde_json::json!({
                "name": params.name,
                "old_name": project.name,
                "username": oper.username,
                "user_uid": user_uid,
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        let history = project_history_name::ActiveModel {
            id: NotSet,
            project_uid: Set(project.id),
            history_name: Set(project.name),
            changed_at: Set(Utc::now()),
        };
        history.insert(&txn).await?;
        log.insert(&txn).await?;
        txn.commit().await?;
        Ok(())
    }

    pub async fn project_exchange_visibility(
        &self,
        ctx: &Session,
        project_name: String,
        params: ExchangeProjectVisibility,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .await?;
        if let Ok(role) = self.utils_project_context_role(&ctx, project_name).await {
            if role == MemberRole::Member {
                return Err(AppError::NoPower);
            }
        } else {
            return Err(AppError::NoPower);
        }

        if project.is_public != params.is_public {
            let txn = self.db.begin().await?;
            let mut active = project.clone().into_active_model();
            active.is_public = Set(params.is_public);
            active.update(&txn).await?;

            let oper = user::Entity::find()
                .filter(user::Column::Uid.eq(user_uid))
                .one(&txn)
                .await?
                .ok_or(AppError::UserNotFound)?;

            let log = project_audit_log::ActiveModel {
                project: Set(project.id),
                actor: Set(user_uid),
                action: Set("exchange_visibility".to_string()),
                details: Set(Some(serde_json::json!({
                    "is_public": params.is_public,
                    "old_is_public": project.is_public,
                    "username": oper.username,
                    "user_uid": user_uid,
                }))),
                created_at: Set(Utc::now()),
                ..Default::default()
            };
            log.insert(&txn).await?;
            txn.commit().await?;
        }
        Ok(())
    }

    pub async fn project_exchange_title(
        &self,
        ctx: &Session,
        project_name: String,
        params: ExchangeProjectTitle,
    ) -> Result<(), AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
        let project = self
            .utils_find_project_by_name(project_name.clone())
            .await?;
        if let Ok(role) = self.utils_project_context_role(&ctx, project_name).await {
            if role == MemberRole::Member {
                return Err(AppError::NoPower);
            }
        } else {
            return Err(AppError::NoPower);
        }

        let txn = self.db.begin().await?;
        let mut active = project.clone().into_active_model();
        if let Some(description) = params.description.clone() {
            active.description = Set(Some(description));
        }
        if let Some(display_name) = params.display_name.clone() {
            active.display_name = Set(display_name);
        }
        active.update(&txn).await?;

        let oper = user::Entity::find()
            .filter(user::Column::Uid.eq(user_uid))
            .one(&txn)
            .await?
            .ok_or(AppError::UserNotFound)?;

        let log = project_audit_log::ActiveModel {
            project: Set(project.id),
            actor: Set(user_uid),
            action: Set("exchange_description".to_string()),
            details: Set(Some(serde_json::json!({
                "description": params.description,
                "username": oper.username,
                "user_uid": user_uid,
                "old_description": project.description,
                "display_name": params.display_name,
                "old_display_name": project.display_name,
            }))),
            created_at: Set(Utc::now()),
            ..Default::default()
        };
        log.insert(&txn).await?;
        txn.commit().await?;
        Ok(())
    }
}
