use crate::AppService;
use crate::error::AppError;
use models::projects::{MemberRole, ProjectMember, project_audit_log, project_members};
use models::repos::repo;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;

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

#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct TransferRepoResponse {
    pub repo_id: Uuid,
    pub old_project_name: String,
    pub new_project_name: String,
    pub repo_name: String,
}

impl AppService {
    pub async fn transfer_repo(
        &self,
        ctx: &Session,
        source_project_name: String,
        repo_name: String,
        params: TransferRepoParams,
    ) -> Result<TransferRepoResponse, AppError> {
        let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;

        let source_project = self
            .utils_find_project_by_name(source_project_name.clone())
            .await?;
        let target_project = self
            .utils_find_project_by_name(params.target_project_name.clone())
            .await?;

        let source_member = ProjectMember::find()
            .filter(project_members::Column::Project.eq(source_project.id))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::PermissionDenied)?;

        if source_member
            .scope_role()
            .map_err(|_| AppError::InternalError)?
            != MemberRole::Owner
            && source_member
                .scope_role()
                .map_err(|_| AppError::InternalError)?
                != MemberRole::Admin
        {
            return Err(AppError::PermissionDenied);
        }

        let target_member = ProjectMember::find()
            .filter(project_members::Column::Project.eq(target_project.id))
            .filter(project_members::Column::User.eq(user_uid))
            .one(&self.db)
            .await?
            .ok_or(AppError::PermissionDenied)?;

        if target_member
            .scope_role()
            .map_err(|_| AppError::InternalError)?
            != MemberRole::Owner
            && target_member
                .scope_role()
                .map_err(|_| AppError::InternalError)?
                != MemberRole::Admin
        {
            return Err(AppError::PermissionDenied);
        }

        let repository = repo::Entity::find()
            .filter(repo::Column::RepoName.eq(repo_name.clone()))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Repository not found".to_string()))?;

        if repository.project != source_project.id {
            return Err(AppError::NotFound(
                "Repository not found in source project".to_string(),
            ));
        }

        let target_repo_exists = repo::Entity::find()
            .filter(repo::Column::RepoName.eq(repo_name.clone()))
            .one(&self.db)
            .await?;

        if target_repo_exists.is_some() {
            return Err(AppError::InternalServerError(
                "Repository with same name already exists in target project".to_string(),
            ));
        }

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

        let old_project_name = source_project.name.clone();
        let mut active_repo: repo::ActiveModel = repository.clone().into();
        active_repo.project = Set(target_project.id);

        let updated_repo = active_repo.update(&txn).await?;

        let source_log = project_audit_log::ActiveModel {
            project: Set(source_project.id),
            actor: Set(user_uid),
            action: Set("repo_transfer_out".to_string()),
            details: Set(Some(serde_json::json!({
                "repo_id": repository.id,
                "repo_name": repo_name,
                "target_project": target_project.name,
            }))),
            created_at: Set(chrono::Utc::now()),
            ..Default::default()
        };
        source_log.insert(&txn).await?;

        let target_log = project_audit_log::ActiveModel {
            project: Set(target_project.id),
            actor: Set(user_uid),
            action: Set("repo_transfer_in".to_string()),
            details: Set(Some(serde_json::json!({
                "repo_id": repository.id,
                "repo_name": repo_name,
                "source_project": source_project.name,
            }))),
            created_at: Set(chrono::Utc::now()),
            ..Default::default()
        };
        target_log.insert(&txn).await?;

        txn.commit().await?;

        let _ = self
            .project_log_activity(
                source_project.id,
                Some(updated_repo.id),
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "repo_transfer_out".to_string(),
                    title: format!(
                        "{} transferred repository '{}' to project '{}'",
                        user_uid, repo_name, target_project.name
                    ),
                    repo_id: Some(updated_repo.id),
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "repo_name": repo_name,
                        "target_project": target_project.name,
                    })),
                    is_private: false,
                },
            )
            .await;
        let _ = self
            .project_log_activity(
                target_project.id,
                Some(updated_repo.id),
                user_uid,
                super::activity::ActivityLogParams {
                    event_type: "repo_transfer_in".to_string(),
                    title: format!(
                        "{} transferred repository '{}' from project '{}'",
                        user_uid, repo_name, source_project.name
                    ),
                    repo_id: Some(updated_repo.id),
                    content: None,
                    event_id: None,
                    event_sub_id: None,
                    metadata: Some(serde_json::json!({
                        "repo_name": repo_name,
                        "source_project": source_project.name,
                    })),
                    is_private: false,
                },
            )
            .await;

        Ok(TransferRepoResponse {
            repo_id: updated_repo.id,
            old_project_name,
            new_project_name: target_project.name,
            repo_name,
        })
    }
}
