use serde::{Deserialize, Serialize};
use std::{
    fmt::{Display, Formatter, Result as FmtResult},
    str::FromStr,
};
use utoipa::ToSchema;

/// The System Actors
///
/// Standard actors used to validate operations during the authorization process
/// in system use-cases.
///
#[derive(Debug, Clone, Deserialize, Serialize, ToSchema, Eq, PartialEq)]
#[serde(rename_all = "camelCase")]
pub enum SystemActor {
    /// Beginner
    ///
    /// This actor is used when no role is assigned to the user.
    Beginner,

    /// Subscriptions manager
    ///
    /// This actor is responsible for managing subscriptions accounts.
    SubscriptionsManager,

    /// Users account manager
    ///
    /// This actor is responsible for managing users accounts.
    UsersManager,

    /// Account manager
    ///
    /// This actor is responsible for managing a single subscription account.
    AccountManager,

    /// Guest manager
    ///
    /// This actor is responsible for managing roles, guest-roles, and
    /// guest-users.
    GuestsManager,

    /// Gateway manager
    ///
    /// This actor is responsible for managing gateway endpoints and related
    /// configurations.
    GatewayManager,

    /// System manager
    ///
    /// This actor is responsible for managing system, including error messages,
    /// webhooks, and others.
    SystemManager,

    /// Tenant owner
    ///
    /// This actor is responsible for managing tenant metadata, tags, and owner.
    ///
    /// WARNING: This is not a role in the system. Don't use to filter licensed
    /// resource scopes during the profile checking.
    ///
    /// ❌ Wrong example:
    ///
    /// ```rust
    /// use myc_core::domain::dtos::profile::Profile;
    /// use myc_core::domain::actors::SystemActor;
    /// use uuid::Uuid;
    ///
    /// let profile = Profile::default();
    /// let tenant_id = Uuid::new_v4();
    ///
    /// let related_accounts = profile
    ///     .on_tenant(tenant_id)
    ///     .with_system_accounts_access()
    ///     .with_write_access()
    ///     .with_roles(vec![
    ///         SystemActor::TenantOwner,
    ///         SystemActor::TenantManager,
    ///         SystemActor::SubscriptionsManager,
    ///     ])
    ///     .get_related_account_or_error();
    /// ```
    ///
    /// This way should check if the profile has access to the tenant as a guest
    /// role. However, tenant owner should be guest as a ownership not as a
    /// licensed resource.
    ///
    /// ✅ Right example:
    ///
    /// ```rust
    /// use myc_core::domain::dtos::profile::Profile;
    /// use myc_core::domain::actors::SystemActor;
    /// use myc_core::domain::dtos::guest_role::Permission;
    /// use uuid::Uuid;
    ///
    /// let profile = Profile::default();
    /// let tenant_id = Uuid::new_v4();
    ///
    /// let related_accounts = profile
    ///     .on_tenant(tenant_id)
    ///     .with_system_accounts_access()
    ///     .with_write_access()
    ///     .with_roles(vec![
    ///         SystemActor::TenantManager,
    ///         SystemActor::SubscriptionsManager,
    ///     ])
    ///     .get_related_accounts_or_tenant_wide_permission_or_error(tenant_id, Permission::Write);
    /// ```
    ///
    /// This way should check if the profile has ownership over the tenant.
    ///
    TenantOwner,

    /// Tenant manager
    ///
    /// This actor is responsible for managing tenants.
    TenantManager,

    /// Service
    ///
    /// This is a service entity.
    Service,

    /// Custom role
    #[serde(untagged)]
    CustomRole(String),
}

impl Display for SystemActor {
    fn fmt(&self, f: &mut Formatter) -> FmtResult {
        match self {
            SystemActor::CustomRole(role) => write!(f, "{}", role),
            SystemActor::Beginner => write!(f, "beginners"),
            SystemActor::SubscriptionsManager => {
                write!(f, "subscriptions-manager")
            }
            SystemActor::UsersManager => {
                write!(f, "users-manager")
            }
            SystemActor::AccountManager => {
                write!(f, "accounts-manager")
            }
            SystemActor::GuestsManager => {
                write!(f, "guests-manager")
            }
            SystemActor::GatewayManager => {
                write!(f, "gateway-manager")
            }
            SystemActor::SystemManager => write!(f, "system-manager"),
            SystemActor::TenantOwner => write!(f, "tenant-owner"),
            SystemActor::TenantManager => write!(f, "tenant-manager"),
            SystemActor::Service => write!(f, "service"),
        }
    }
}

impl FromStr for SystemActor {
    type Err = ();

    fn from_str(s: &str) -> Result<SystemActor, ()> {
        match s {
            "beginners" | "beginner" | "no-role" => Ok(SystemActor::Beginner),
            "subscriptions-account-manager" | "subscriptions-manager" => {
                Ok(SystemActor::SubscriptionsManager)
            }
            "users-account-manager" | "users-manager" => {
                Ok(SystemActor::UsersManager)
            }
            "accounts-manager" | "account-manager" => {
                Ok(SystemActor::AccountManager)
            }
            "guests-manager" | "guest-manager" => {
                Ok(SystemActor::GuestsManager)
            }
            "gateway-manager" => Ok(SystemActor::GatewayManager),
            "system-manager" => Ok(SystemActor::SystemManager),
            "tenant-manager" => Ok(SystemActor::TenantManager),
            "tenant-owner" => Ok(SystemActor::TenantOwner),
            "service" => Ok(SystemActor::Service),

            other => Ok(SystemActor::CustomRole(other.to_string())),
        }
    }
}

impl SystemActor {
    pub fn str(&self) -> &str {
        match self {
            SystemActor::CustomRole(role) => role,
            SystemActor::Beginner => "beginners",
            SystemActor::SubscriptionsManager => "subscriptions-manager",
            SystemActor::UsersManager => "users-manager",
            SystemActor::AccountManager => "accounts-manager",
            SystemActor::GuestsManager => "guests-manager",
            SystemActor::GatewayManager => "gateway-manager",
            SystemActor::SystemManager => "system-manager",
            SystemActor::TenantOwner => "tenant-owner",
            SystemActor::TenantManager => "tenant-manager",
            SystemActor::Service => "service",
        }
    }
}
