// !!!
// WARNING: This file is autogenerated
// Only modify code within MANUAL() sections
// or your changes may be overwritten later!
// !!!

use crate::consumer::m2m_clients::Clients;
use serde::{Deserialize, Serialize};

use crate::shared::jwt_helpers::{authenticate_jwt, JWTError};

/// M2MClient:
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct M2MClient {
    /// client_id: The ID of the client.
    pub client_id: String,
    /// client_name: A human-readable name for the client.
    pub client_name: String,
    /// client_description: A human-readable description for the client.
    pub client_description: String,
    /// status: The status of the client - either `active` or `inactive`.
    pub status: String,
    /// scopes: An array of scopes assigned to the client.
    pub scopes: std::vec::Vec<String>,
    /// client_secret_last_four: The last four characters of the client secret.
    pub client_secret_last_four: String,
    /// trusted_metadata: An arbitrary JSON object for storing application-specific data.
    pub trusted_metadata: std::option::Option<serde_json::Value>,
    /// next_client_secret_last_four: The last four characters of the `next_client_secret`. Null if no
    /// `next_client_secret` exists.
    pub next_client_secret_last_four: std::option::Option<String>,
}
/// M2MClientWithClientSecret:
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct M2MClientWithClientSecret {
    /// client_id: The ID of the client.
    pub client_id: String,
    /// client_secret: The secret of the client. **Important:** this is the only time you will be able to view
    /// the `client_secret`. Be sure to persist the `client_secret` in a secure location. If the `client_secret`
    /// is lost, you will need to trigger a secret rotation flow to receive another one.
    pub client_secret: String,
    /// client_name: A human-readable name for the client.
    pub client_name: String,
    /// client_description: A human-readable description for the client.
    pub client_description: String,
    /// status: The status of the client - either `active` or `inactive`.
    pub status: String,
    /// scopes: An array of scopes assigned to the client.
    pub scopes: std::vec::Vec<String>,
    /// client_secret_last_four: The last four characters of the client secret.
    pub client_secret_last_four: String,
    /// trusted_metadata: An arbitrary JSON object for storing application-specific data.
    pub trusted_metadata: std::option::Option<serde_json::Value>,
    /// next_client_secret_last_four: The last four characters of the `next_client_secret`. Null if no
    /// `next_client_secret` exists.
    pub next_client_secret_last_four: std::option::Option<String>,
}
/// M2MClientWithNextClientSecret:
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct M2MClientWithNextClientSecret {
    /// client_id: The ID of the client.
    pub client_id: String,
    /// next_client_secret: The newly created secret that's next in rotation for the client. **Important:** this
    /// is the only time you will be able to view the `next_client_secret`. Be sure to persist the
    /// `next_client_secret` in a secure location. If the `next_client_secret` is lost, you will need to trigger
    /// a secret rotation flow to receive another one.
    pub next_client_secret: String,
    /// client_name: A human-readable name for the client.
    pub client_name: String,
    /// client_description: A human-readable description for the client.
    pub client_description: String,
    /// status: The status of the client - either `active` or `inactive`.
    pub status: String,
    /// scopes: An array of scopes assigned to the client.
    pub scopes: std::vec::Vec<String>,
    /// client_secret_last_four: The last four characters of the client secret.
    pub client_secret_last_four: String,
    /// trusted_metadata: An arbitrary JSON object for storing application-specific data.
    pub trusted_metadata: std::option::Option<serde_json::Value>,
    /// next_client_secret_last_four: The last four characters of the `next_client_secret`. Null if no
    /// `next_client_secret` exists.
    pub next_client_secret_last_four: std::option::Option<String>,
}
/// M2MSearchQuery:
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct M2MSearchQuery {
    /// operator: The action to perform on the operands. The accepted value are:
    ///
    ///   `AND` – all the operand values provided must match.
    ///
    ///   `OR` – the operator will return any matches to at least one of the operand values you supply.
    pub operator: M2MSearchQueryOperator,
    /// operands: An array of operand objects that contains all of the filters and values to apply to your
    /// search search query.
    pub operands: std::vec::Vec<serde_json::Value>,
}
/// ResultsMetadata:
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ResultsMetadata {
    /// total: The total number of results returned by your search query. If totals have been disabled for your
    /// Stytch Workspace to improve search performance, the value will always be -1.
    pub total: i32,
    /// next_cursor: The `next_cursor` string is returned when your search result contains more than one page of
    /// results. This value is passed into your next search call in the `cursor` field.
    pub next_cursor: std::option::Option<String>,
}

#[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub enum M2MSearchQueryOperator {
    #[serde(rename = "OR")]
    #[default]
    OR,
    #[serde(rename = "AND")]
    AND,
}

// MANUAL(token_types)(TYPES)
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TokenRequest {
    pub client_id: String,
    pub client_secret: String,
    pub scopes: std::option::Option<std::vec::Vec<String>>,
    pub grant_type: String,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct TokenResponse {
    pub access_token: String,
    pub token_type: String,
    pub expires_in: i32,
}
// ENDMANUAL(token_types)

// MANUAL(authentication_token_types)(TYPES)
pub struct AuthorizationCheckParams {
    pub has_scopes: std::vec::Vec<String>,
    pub required_scopes: std::vec::Vec<String>,
}

#[derive(Default)]
pub struct AuthenticateTokenRequest {
    pub access_token: String,
    pub required_scopes: std::option::Option<std::vec::Vec<String>>,
    pub max_token_age_seconds: std::option::Option<u64>,
    pub scope_authorization_func: std::option::Option<fn(&AuthorizationCheckParams) -> bool>,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct AuthenticateTokenResponse {
    pub client_id: String,
    pub scopes: std::vec::Vec<String>,
    pub custom_claims: std::collections::HashMap<String, serde_json::Value>,
}
// ENDMANUAL(authentication_token_types)

// MANUAL(perform_authorization_check)(FREE_FUNCTION)
fn perform_authorization_check(params: &AuthorizationCheckParams) -> bool {
    let mut client_scopes: std::collections::HashMap<String, std::collections::HashSet<String>> =
        std::collections::HashMap::new();

    for scope in &params.has_scopes {
        let (action, resource) = split_scope(scope);
        client_scopes.entry(action).or_default().insert(resource);
    }

    // Check if all required scopes are satisfied
    params.required_scopes.iter().all(|required_scope| {
        let (required_action, required_resource) = split_scope(required_scope);
        client_scopes
            .get(&required_action)
            .map_or(false, |resources| {
                resources.contains("*") || resources.contains(&required_resource)
            })
    })
}

// Helper function to split a scope into action and resource parts
fn split_scope(scope: &str) -> (String, String) {
    if let Some(pos) = scope.find(':') {
        let (action, resource) = scope.split_at(pos);
        (action.to_string(), resource[1..].to_string())
    } else {
        (scope.to_string(), "-".to_string())
    }
}
// ENDMANUAL(perform_authorization_check)

pub struct M2M {
    http_client: crate::client::Client,
    pub clients: Clients,
}

impl M2M {
    pub fn new(http_client: crate::client::Client) -> Self {
        Self {
            http_client: http_client.clone(),
            clients: Clients::new(http_client.clone()),
        }
    }

    // MANUAL(m2m.token)(SERVICE_METHOD)
    pub async fn token(&self, body: TokenRequest) -> crate::Result<TokenResponse> {
        /*
        """Retrieves an access token for the given M2M Client.
        Access tokens are JWTs signed with the project's JWKs, and are valid for one hour after issuance.
        M2M Access tokens contain a standard set of claims as well as any custom claims generated from templates.

        Fields:
          - client_id: The ID of the client.
          - client_secret: The secret of the client.
          - scopes: An array of scopes requested. If omitted, all scopes assigned to the client will be returned.
        """  # noqa
        */

        let project_id = &self.http_client.project_id;
        let path = format!("/v1/public/{project_id}/oauth2/token");
        self.http_client
            .send(crate::Request {
                method: http::Method::POST,
                path,
                body,
            })
            .await
    }
    // ENDMANUAL(m2m.token)

    // MANUAL(m2m.authenticate_token)(SERVICE_METHOD)
    // ADDIMPORT: use crate::shared::jwt_helpers::{authenticate_jwt, JWTError};
    pub async fn authenticate_token(
        &self,
        body: AuthenticateTokenRequest,
    ) -> crate::Result<AuthenticateTokenResponse> {
        let _scope_claim = "scope";
        let generic_claims = authenticate_jwt(
            &self.http_client,
            &body.access_token,
            body.max_token_age_seconds,
        )
        .await?;

        let scope_value = generic_claims
            .untyped_claims
            .get(_scope_claim)
            .ok_or(JWTError::MissingField(_scope_claim.to_owned()))?
            .to_owned();
        let scope: String =
            serde_json::from_value(scope_value).map_err(|_| JWTError::PayloadFormat)?;
        let scopes: Vec<String> = scope.split_whitespace().map(|s| s.to_string()).collect();
        let required_scopes = body.required_scopes.unwrap_or_default();

        let scope_authorization_func = if let Some(f) = body.scope_authorization_func {
            f
        } else {
            perform_authorization_check
        };
        let is_authorized = scope_authorization_func(&AuthorizationCheckParams {
            has_scopes: scopes.clone(),
            required_scopes: required_scopes.clone(),
        });

        if !is_authorized {
            return Err(crate::Error::Unauthorized);
        }

        let sub_claim = generic_claims.reserved_claims["sub"].to_owned();
        let client_id = serde_json::from_value(sub_claim).map_err(|_| JWTError::PayloadFormat)?;
        let custom_claims: std::collections::HashMap<String, serde_json::Value> = generic_claims
            .untyped_claims
            .iter()
            .filter_map(|(k, v)| {
                if k != _scope_claim {
                    Some((k.clone(), v.clone()))
                } else {
                    None
                }
            })
            .collect();

        Ok(AuthenticateTokenResponse {
            client_id,
            scopes,
            custom_claims,
        })
    }
    // ENDMANUAL(m2m.authenticate_token)
}
