use serde::{Deserialize, Serialize};

pub const DEFAULT_TENANT_ID: &str = "default";

fn default_tenant_id() -> String {
    DEFAULT_TENANT_ID.to_string()
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Tenant {
    pub id: String,
    pub enabled: bool,
}

impl Tenant {
    pub fn active(id: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            enabled: true,
        }
    }
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum PrincipalKind {
    LocalUser,
    ServiceAccount,
    FederatedWebIdentity,
    LdapUser,
    ActiveDirectoryUser,
    SamlSubject,
    MtlsClient,
    CustomIdentity,
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct Principal {
    pub id: String,
    pub kind: PrincipalKind,
    #[serde(default = "default_tenant_id")]
    pub tenant_id: String,
    pub enabled: bool,
}

impl Principal {
    pub fn local_user(id: impl Into<String>) -> Self {
        Self::local_user_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn local_user_in_tenant(id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            kind: PrincipalKind::LocalUser,
            tenant_id: tenant_id.into(),
            enabled: true,
        }
    }

    pub fn service_account(id: impl Into<String>) -> Self {
        Self::service_account_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn service_account_in_tenant(id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            kind: PrincipalKind::ServiceAccount,
            tenant_id: tenant_id.into(),
            enabled: true,
        }
    }

    pub fn federated_web_identity(id: impl Into<String>) -> Self {
        Self::federated_web_identity_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn federated_web_identity_in_tenant(
        id: impl Into<String>,
        tenant_id: impl Into<String>,
    ) -> Self {
        Self::with_kind(id, PrincipalKind::FederatedWebIdentity, tenant_id)
    }

    pub fn ldap_user(id: impl Into<String>) -> Self {
        Self::ldap_user_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn ldap_user_in_tenant(id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
        Self::with_kind(id, PrincipalKind::LdapUser, tenant_id)
    }

    pub fn active_directory_user(id: impl Into<String>) -> Self {
        Self::active_directory_user_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn active_directory_user_in_tenant(
        id: impl Into<String>,
        tenant_id: impl Into<String>,
    ) -> Self {
        Self::with_kind(id, PrincipalKind::ActiveDirectoryUser, tenant_id)
    }

    pub fn saml_subject(id: impl Into<String>) -> Self {
        Self::saml_subject_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn saml_subject_in_tenant(id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
        Self::with_kind(id, PrincipalKind::SamlSubject, tenant_id)
    }

    pub fn mtls_client(id: impl Into<String>) -> Self {
        Self::mtls_client_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn mtls_client_in_tenant(id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
        Self::with_kind(id, PrincipalKind::MtlsClient, tenant_id)
    }

    pub fn custom_identity(id: impl Into<String>) -> Self {
        Self::custom_identity_in_tenant(id, DEFAULT_TENANT_ID)
    }

    pub fn custom_identity_in_tenant(id: impl Into<String>, tenant_id: impl Into<String>) -> Self {
        Self::with_kind(id, PrincipalKind::CustomIdentity, tenant_id)
    }

    pub fn with_kind(
        id: impl Into<String>,
        kind: PrincipalKind,
        tenant_id: impl Into<String>,
    ) -> Self {
        Self {
            id: id.into(),
            kind,
            tenant_id: tenant_id.into(),
            enabled: true,
        }
    }
}

#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct CustomIdentity {
    pub principal_id: String,
    shared_secret: String,
    pub enabled: bool,
}

impl CustomIdentity {
    pub fn active(principal_id: impl Into<String>, shared_secret: impl Into<String>) -> Self {
        Self {
            principal_id: principal_id.into(),
            shared_secret: shared_secret.into(),
            enabled: true,
        }
    }

    pub fn disable(&mut self) {
        self.enabled = false;
    }

    pub fn verify_secret(&self, shared_secret: &str) -> bool {
        self.enabled && self.shared_secret == shared_secret
    }

    pub fn parent_secret(&self) -> String {
        format!("bucketwarden-custom-identity-parent:{}", self.principal_id)
    }
}
