use crate::AppService;
use crate::errors::AppError;
use session::Session;
use models::active_enums::MemberRole;
use models::project::{project_members, projects};
use models::repos::repo;
use sea_orm::*;
use uuid::Uuid;

pub enum RepoPermission {
    Read,
    Write,
}

impl AppService {
    pub async fn check_repo_permission(
        &self,
        ctx: &Session,
        namespace: &str,
        repo_name: &str,
        required_permission: RepoPermission,
    ) -> Result<(repo::Model, projects::Model), AppError> {
        let repository = repo::Entity::find()
            .filter(repo::Column::Namespace.eq(namespace))
            .filter(repo::Column::RepoName.eq(repo_name))
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Repository not found".to_string()))?;

        let project = projects::Entity::find()
            .filter(projects::Column::Name.eq(namespace))
            .one(&self.db)
            .await?
            .ok_or(AppError::ProjectNotFound)?;

        if repository.project_uid != Some(project.uid) {
            return Err(AppError::NotFound(
                "Repository not found in project".to_string(),
            ));
        }

        let user_uid = ctx.user();

        match required_permission {
            RepoPermission::Read => {
                if project.is_public && !repository.is_private {
                    return Ok((repository, project));
                }

                let user_uid = user_uid.ok_or(AppError::Unauthorized)?;

                let member = project_members::Entity::find()
                    .filter(project_members::Column::ProjectId.eq(project.uid))
                    .filter(project_members::Column::UserId.eq(user_uid))
                    .one(&self.db)
                    .await?;

                if member.is_some() {
                    Ok((repository, project))
                } else {
                    Err(AppError::PermissionDenied)
                }
            }
            RepoPermission::Write => {
                let user_uid = user_uid.ok_or(AppError::Unauthorized)?;

                let member = project_members::Entity::find()
                    .filter(project_members::Column::ProjectId.eq(project.uid))
                    .filter(project_members::Column::UserId.eq(user_uid))
                    .one(&self.db)
                    .await?
                    .ok_or(AppError::PermissionDenied)?;

                if member.scope == MemberRole::Owner || member.scope == MemberRole::Admin {
                    Ok((repository, project))
                } else {
                    Err(AppError::PermissionDenied)
                }
            }
        }
    }

    pub async fn check_repo_read_permission(
        &self,
        ctx: &Session,
        namespace: &str,
        repo_name: &str,
    ) -> Result<(repo::Model, projects::Model), AppError> {
        self.check_repo_permission(ctx, namespace, repo_name, RepoPermission::Read)
            .await
    }

    pub async fn check_repo_write_permission(
        &self,
        ctx: &Session,
        namespace: &str,
        repo_name: &str,
    ) -> Result<(repo::Model, projects::Model), AppError> {
        self.check_repo_permission(ctx, namespace, repo_name, RepoPermission::Write)
            .await
    }

    pub async fn check_repo_permission_by_uid(
        &self,
        ctx: &Session,
        repo_uid: Uuid,
        required_permission: RepoPermission,
    ) -> Result<(repo::Model, projects::Model), AppError> {
        let repository = repo::Entity::find_by_id(repo_uid)
            .one(&self.db)
            .await?
            .ok_or(AppError::NotFound("Repository not found".to_string()))?;

        let project_uid = repository.project_uid.ok_or(AppError::NotFound(
            "Repository has no associated project".to_string(),
        ))?;

        let project = projects::Entity::find_by_id(project_uid)
            .one(&self.db)
            .await?
            .ok_or(AppError::ProjectNotFound)?;

        let user_uid = ctx.user();

        match required_permission {
            RepoPermission::Read => {
                if project.is_public && !repository.is_private {
                    return Ok((repository, project));
                }

                let user_uid = user_uid.ok_or(AppError::Unauthorized)?;

                let member = project_members::Entity::find()
                    .filter(project_members::Column::ProjectId.eq(project.uid))
                    .filter(project_members::Column::UserId.eq(user_uid))
                    .one(&self.db)
                    .await?;

                if member.is_some() {
                    Ok((repository, project))
                } else {
                    Err(AppError::PermissionDenied)
                }
            }
            RepoPermission::Write => {
                let user_uid = user_uid.ok_or(AppError::Unauthorized)?;

                let member = project_members::Entity::find()
                    .filter(project_members::Column::ProjectId.eq(project.uid))
                    .filter(project_members::Column::UserId.eq(user_uid))
                    .one(&self.db)
                    .await?
                    .ok_or(AppError::PermissionDenied)?;

                if member.scope == MemberRole::Owner || member.scope == MemberRole::Admin {
                    Ok((repository, project))
                } else {
                    Err(AppError::PermissionDenied)
                }
            }
        }
    }
}
