//! Public OAuth2 token endpoints — no tenant headers required.

use crate::zeroid::{
    client::{parse_api_error, ZeroIDClient},
    error::ZeroIDError,
    types::{
        AccessToken, HealthResponse, IntrospectRequest, JwksResponse, RevokeTokenRequest,
        TokenIntrospection, TokenRequest, TokenRevoked,
    },
};

/// Tokens resource — public OAuth2 endpoints on `/oauth2/token`.
pub struct TokensResource<'a> {
    pub(crate) client: &'a ZeroIDClient,
}

impl<'a> TokensResource<'a> {
    /// Issue an access token.
    ///
    /// `POST /oauth2/token`
    pub async fn issue(&self, request: &TokenRequest) -> Result<AccessToken, ZeroIDError> {
        let body = serde_json::to_value(request)?;
        let headers = self.client.public_headers();
        let resp = self
            .client
            .send_with_retry(reqwest::Method::POST, "/oauth2/token", headers, Some(&body))
            .await?;
        if !resp.status().is_success() {
            return Err(parse_api_error(resp).await);
        }
        Ok(resp.json().await?)
    }

    /// Introspect a token (RFC 7662).
    ///
    /// `POST /oauth2/token/introspect`
    pub async fn introspect(
        &self,
        request: &IntrospectRequest,
    ) -> Result<TokenIntrospection, ZeroIDError> {
        let body = serde_json::to_value(request)?;
        let headers = self.client.public_headers();
        let resp = self
            .client
            .send_with_retry(
                reqwest::Method::POST,
                "/oauth2/token/introspect",
                headers,
                Some(&body),
            )
            .await?;
        if !resp.status().is_success() {
            return Err(parse_api_error(resp).await);
        }
        Ok(resp.json().await?)
    }

    /// Delegate to a sub-agent via RFC 8693 token exchange.
    ///
    /// Uses the client's cached access token (from the api_key) as the subject_token.
    /// Requires the client to be initialized with an api_key.
    pub async fn exchange(
        &self,
        actor_token: &str,
        scope: &str,
    ) -> Result<AccessToken, ZeroIDError> {
        let subject_token = self.client.get_token().await?;
        let request = TokenRequest {
            grant_type: "urn:ietf:params:oauth:grant-type:token-exchange".to_string(),
            subject_token: Some(subject_token),
            actor_token: Some(actor_token.to_string()),
            scope: Some(scope.to_string()),
            ..Default::default()
        };
        self.issue(&request).await
    }

    /// Revoke a token (RFC 7009).
    ///
    /// `POST /oauth2/token/revoke`
    pub async fn revoke(&self, request: &RevokeTokenRequest) -> Result<TokenRevoked, ZeroIDError> {
        let body = serde_json::to_value(request)?;
        let headers = self.client.public_headers();
        let resp = self
            .client
            .send_with_retry(
                reqwest::Method::POST,
                "/oauth2/token/revoke",
                headers,
                Some(&body),
            )
            .await?;
        if !resp.status().is_success() {
            return Err(parse_api_error(resp).await);
        }
        Ok(resp.json().await?)
    }

    /// Fetch the JSON Web Key Set.
    ///
    /// `GET /.well-known/jwks.json`
    pub async fn jwks(&self) -> Result<JwksResponse, ZeroIDError> {
        let headers = self.client.public_headers();
        let resp = self
            .client
            .send_with_retry(
                reqwest::Method::GET,
                "/.well-known/jwks.json",
                headers,
                None,
            )
            .await?;
        if !resp.status().is_success() {
            return Err(parse_api_error(resp).await);
        }
        Ok(resp.json().await?)
    }

    /// Health check.
    ///
    /// `GET /health`
    pub async fn health(&self) -> Result<HealthResponse, ZeroIDError> {
        let headers = self.client.public_headers();
        let resp = self
            .client
            .send_with_retry(reqwest::Method::GET, "/health", headers, None)
            .await?;
        if !resp.status().is_success() {
            return Err(parse_api_error(resp).await);
        }
        Ok(resp.json().await?)
    }
}
