use super::*;

impl AuthStore {
    pub fn assume_role_with_web_identity(
        &mut self,
        request: AssumeRoleWithWebIdentityRequest,
        now_epoch_seconds: u64,
    ) -> Result<SessionCredential, AuthError> {
        let provider = self
            .identity_providers
            .get(&request.provider_id)
            .ok_or_else(|| AuthError::UnknownIdentityProvider(request.provider_id.clone()))?;
        let subject = provider.verify_subject(&request.web_identity_token, now_epoch_seconds)?;
        let principal_id = provider.principal_id(&subject);
        let parent_secret = provider.parent_secret();
        if !self.principals.contains_key(&principal_id) {
            self.upsert_principal(provider.principal(&subject))?;
        }
        let parent_access_key_id = format!("web-identity-parent:{}", request.provider_id);
        if !self.credentials.contains_key(&parent_access_key_id) {
            self.put_access_key(AccessKey::active(
                principal_id.clone(),
                parent_access_key_id.clone(),
                parent_secret,
            ))?;
        }
        let expires_at_epoch_seconds = now_epoch_seconds.saturating_add(request.duration_seconds);
        let session = SessionCredential::new(
            principal_id,
            parent_access_key_id,
            request.access_key_id,
            request.secret_access_key,
            request.session_token,
            expires_at_epoch_seconds,
        )
        .with_scope(request.scope);
        self.put_session(session.clone())?;
        Ok(session)
    }

    pub fn assume_role_with_ldap(
        &mut self,
        request: AssumeRoleWithWebIdentityRequest,
        now_epoch_seconds: u64,
    ) -> Result<SessionCredential, AuthError> {
        match self.identity_providers.get(&request.provider_id) {
            Some(IdentityProvider::Ldap(_)) => {
                self.assume_role_with_web_identity(request, now_epoch_seconds)
            }
            Some(_) => Err(AuthError::UnsupportedIdentityProviderKind(
                request.provider_id,
            )),
            None => Err(AuthError::UnknownIdentityProvider(request.provider_id)),
        }
    }

    pub fn assume_role(
        &mut self,
        request: AssumeRoleRequest,
        now_epoch_seconds: u64,
    ) -> Result<SessionCredential, AuthError> {
        self.require_enabled_principal(&request.principal_id)?;
        if !self.operator_action_allowed(
            &request.principal_id,
            OperatorAction::ManageCredentials,
            &request.role_resource,
        )? {
            return Err(AuthError::RoleAssumptionDenied {
                principal_id: request.principal_id,
                role_name: request.role_name,
                role_resource: request.role_resource,
            });
        }
        self.issue_scoped_session(
            request.principal_id,
            format!("assume-role-parent:{}", request.role_name),
            format!("bucketwarden-assume-role-parent:{}", request.role_name),
            request.duration_seconds,
            request.scope,
            request.access_key_id,
            request.secret_access_key,
            request.session_token,
            now_epoch_seconds,
        )
    }

    pub fn assume_role_with_custom_identity(
        &mut self,
        request: AssumeRoleWithCustomIdentityRequest,
        now_epoch_seconds: u64,
    ) -> Result<SessionCredential, AuthError> {
        let identity = self
            .custom_identities
            .get(&request.principal_id)
            .ok_or_else(|| AuthError::UnknownCustomIdentity(request.principal_id.clone()))?;
        if !identity.enabled {
            return Err(AuthError::DisabledCustomIdentity(
                request.principal_id.clone(),
            ));
        }
        if !identity.verify_secret(&request.shared_secret) {
            return Err(AuthError::InvalidCustomIdentitySecret(
                request.principal_id.clone(),
            ));
        }
        self.require_enabled_principal(&request.principal_id)?;
        self.issue_scoped_session(
            request.principal_id,
            "custom-identity-parent".to_string(),
            identity.parent_secret(),
            request.duration_seconds,
            request.scope,
            request.access_key_id,
            request.secret_access_key,
            request.session_token,
            now_epoch_seconds,
        )
    }

    pub fn role_assumption_support_report(&self) -> RoleAssumptionSupportReport {
        RoleAssumptionSupportReport::current()
    }

    #[allow(clippy::too_many_arguments)]
    fn issue_scoped_session(
        &mut self,
        principal_id: String,
        parent_access_key_id: String,
        parent_secret: String,
        duration_seconds: u64,
        scope: CredentialScope,
        access_key_id: String,
        secret_access_key: String,
        session_token: String,
        now_epoch_seconds: u64,
    ) -> Result<SessionCredential, AuthError> {
        if !self.credentials.contains_key(&parent_access_key_id) {
            self.put_access_key(AccessKey::active(
                principal_id.clone(),
                parent_access_key_id.clone(),
                parent_secret,
            ))?;
        }
        let expires_at_epoch_seconds = now_epoch_seconds.saturating_add(duration_seconds);
        let session = SessionCredential::new(
            principal_id,
            parent_access_key_id,
            access_key_id,
            secret_access_key,
            session_token,
            expires_at_epoch_seconds,
        )
        .with_scope(scope);
        self.put_session(session.clone())?;
        Ok(session)
    }
}
