use super::*;

impl AuthStore {
    pub fn put_access_key(&mut self, key: AccessKey) -> Result<(), AuthError> {
        self.require_enabled_principal(&key.principal_id)?;
        self.credentials
            .insert(key.access_key_id.clone(), CredentialRecord::AccessKey(key));
        Ok(())
    }

    pub fn put_session(&mut self, session: SessionCredential) -> Result<(), AuthError> {
        self.require_enabled_principal(&session.principal_id)?;
        self.resolve_credential(&session.parent_access_key_id, 0)
            .map_err(|_| AuthError::UnknownParentAccessKey(session.parent_access_key_id.clone()))?;
        self.credentials.insert(
            session.access_key_id.clone(),
            CredentialRecord::Session(session),
        );
        Ok(())
    }

    pub fn put_mtls_client_certificate(
        &mut self,
        certificate: MtlsClientCertificate,
    ) -> Result<(), AuthError> {
        self.require_enabled_principal(&certificate.principal_id)?;
        self.mtls_certificates
            .insert(certificate.certificate_fingerprint.clone(), certificate);
        Ok(())
    }

    pub fn disable_mtls_client_certificate(
        &mut self,
        certificate_fingerprint: &str,
    ) -> Result<(), AuthError> {
        let certificate = self
            .mtls_certificates
            .get_mut(certificate_fingerprint)
            .ok_or_else(|| {
                AuthError::UnknownClientCertificate(certificate_fingerprint.to_string())
            })?;
        certificate.disable();
        Ok(())
    }

    pub fn disable_access_key(&mut self, access_key_id: &str) -> Result<(), AuthError> {
        match self.credential_mut(access_key_id)? {
            CredentialRecord::AccessKey(key) => {
                key.disable();
                Ok(())
            }
            CredentialRecord::Session(_) => Err(AuthError::NotAccessKey(access_key_id.to_string())),
        }
    }

    pub fn revoke_credential(
        &mut self,
        access_key_id: &str,
        now_epoch_seconds: u64,
    ) -> Result<(), AuthError> {
        match self.credential_mut(access_key_id)? {
            CredentialRecord::AccessKey(key) => key.revoke(now_epoch_seconds),
            CredentialRecord::Session(session) => session.revoke(now_epoch_seconds),
        }
        Ok(())
    }

    pub fn revoke_mtls_client_certificate(
        &mut self,
        certificate_fingerprint: &str,
        now_epoch_seconds: u64,
    ) -> Result<(), AuthError> {
        let certificate = self
            .mtls_certificates
            .get_mut(certificate_fingerprint)
            .ok_or_else(|| {
                AuthError::UnknownClientCertificate(certificate_fingerprint.to_string())
            })?;
        certificate.revoke(now_epoch_seconds);
        Ok(())
    }

    pub fn rotate_access_key(
        &mut self,
        old_access_key_id: &str,
        new_access_key_id: impl Into<String>,
        new_secret_access_key: impl Into<String>,
        now_epoch_seconds: u64,
    ) -> Result<CredentialRotation, AuthError> {
        let (principal_id, old_access_key_id) = match self.credential_mut(old_access_key_id)? {
            CredentialRecord::AccessKey(key) => {
                if key.revoked_at_epoch_seconds.is_some() {
                    return Err(AuthError::RevokedCredential(key.access_key_id.clone()));
                }
                key.revoke(now_epoch_seconds);
                (key.principal_id.clone(), key.access_key_id.clone())
            }
            CredentialRecord::Session(_) => {
                return Err(AuthError::NotAccessKey(old_access_key_id.to_string()));
            }
        };
        let new_access_key_id = new_access_key_id.into();
        let mut replacement = AccessKey::active(
            principal_id.clone(),
            new_access_key_id.clone(),
            new_secret_access_key,
        );
        replacement.previous_access_key_id = Some(old_access_key_id.clone());
        replacement.rotated_at_epoch_seconds = Some(now_epoch_seconds);
        self.put_access_key(replacement)?;
        Ok(CredentialRotation {
            old_access_key_id,
            new_access_key_id,
            principal_id,
            rotated_at_epoch_seconds: now_epoch_seconds,
        })
    }

    pub fn report_leaked_access_key(
        &mut self,
        access_key_id: &str,
        now_epoch_seconds: u64,
    ) -> Result<LeakedKeyResponse, AuthError> {
        match self.credential_mut(access_key_id)? {
            CredentialRecord::AccessKey(key) => {
                key.leaked_at_epoch_seconds = Some(now_epoch_seconds);
                key.revoke(now_epoch_seconds);
                Ok(LeakedKeyResponse {
                    access_key_id: key.access_key_id.clone(),
                    principal_id: key.principal_id.clone(),
                    leaked_at_epoch_seconds: now_epoch_seconds,
                    revoked: true,
                })
            }
            CredentialRecord::Session(_) => Err(AuthError::NotAccessKey(access_key_id.to_string())),
        }
    }

    pub fn mark_used(
        &mut self,
        access_key_id: &str,
        now_epoch_seconds: u64,
    ) -> Result<(), AuthError> {
        match self.credential_mut(access_key_id)? {
            CredentialRecord::AccessKey(key) => {
                key.last_used_epoch_seconds = Some(now_epoch_seconds)
            }
            CredentialRecord::Session(session) => {
                session.last_used_epoch_seconds = Some(now_epoch_seconds)
            }
        }
        Ok(())
    }

    pub fn credential(&self, access_key_id: &str) -> Option<&CredentialRecord> {
        self.credentials.get(access_key_id)
    }

    pub fn mtls_certificate(
        &self,
        certificate_fingerprint: &str,
    ) -> Option<&MtlsClientCertificate> {
        self.mtls_certificates.get(certificate_fingerprint)
    }

    pub fn mtls_certificate_count(&self) -> usize {
        self.mtls_certificates.len()
    }

    pub fn credential_count(&self) -> usize {
        self.credentials.len()
    }

    pub fn credential_count_for_tenant(&self, tenant_id: &str) -> usize {
        self.credentials
            .values()
            .filter(|credential| {
                self.principal_tenant_id(credential.principal_id()) == Some(tenant_id)
            })
            .count()
    }
}
