// AUTO-GENERATED — do not edit by hand.
// Source: services.proto  |  Regenerate: make generate  (or: python3 scripts/generators/code/generate.py --lang grpc)

use std::{collections::HashMap, sync::Arc};

use grpc_api_types::payments::{
    // tonic-generated client stubs (one module per service)
{% for service in services %}
    {{ service_to_tonic_mod(service) }}::{{ service }}Client,
{% endfor %}
    // request / response types (all unique types across all services)
{% for t in all_types %}
    {{ t }},
{% endfor %}
};
use tonic::{
    metadata::{MetadataKey, MetadataValue},
    transport::Channel,
    Request, Status,
};

use crate::grpc_config::GrpcConfig;

// ── Internal macro ────────────────────────────────────────────────────────────
//
// Generates a typed sub-client struct for one gRPC service.
// Each method:
//   - creates a fresh tonic stub (Channel is Arc-backed so clone is O(1))
//   - injects all auth headers from the shared header map
//   - returns the unwrapped inner response on success
macro_rules! impl_grpc_client {
    ($name:ident, $stub:ident, $( ($method:ident, $stub_method:ident, $req:ty, $res:ty) ),+ $(,)?) => {
        pub struct $name {
            channel: Channel,
            headers: Arc<HashMap<String, String>>,
        }

        impl $name {
            fn new(channel: Channel, headers: Arc<HashMap<String, String>>) -> Self {
                Self { channel, headers }
            }

            fn inject_headers<T>(&self, payload: T) -> Request<T> {
                let mut req = Request::new(payload);
                for (k, v) in self.headers.as_ref() {
                    if let (Ok(key), Ok(val)) = (
                        MetadataKey::from_bytes(k.as_bytes()),
                        MetadataValue::try_from(v.as_str()),
                    ) {
                        req.metadata_mut().insert(key, val);
                    }
                }
                req
            }

            $(
                pub async fn $method(&self, request: $req) -> Result<$res, Status> {
                    $stub::new(self.channel.clone())
                        .$stub_method(self.inject_headers(request))
                        .await
                        .map(|r| r.into_inner())
                }
            )+
        }
    };
}

// ── Sub-clients (one per proto service) ──────────────────────────────────────

{% for service in services %}
// {{ service }}
impl_grpc_client!(
    {{ service_to_grpc_struct(service) }}, {{ service }}Client,
{% for f in groups[service] %}
    ({{ f.name }}, {{ to_snake_case(f.rpc) }}, {{ f.request }}, {{ f.response }}),
{% endfor %}
);

{% endfor %}
// ── GrpcClient ────────────────────────────────────────────────────────────────

/// Top-level gRPC client for the connector-service.
///
/// All sub-clients share a single underlying connection pool ([`Channel`]).
/// Auth headers from [`GrpcConfig`] are injected automatically on every call.
///
/// # Example
/// ```rust,no_run
/// # use hyperswitch_payments_client::{GrpcClient, GrpcConfig, build_connector_config, ConnectorSpecificConfig};
/// # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> {
/// let client = GrpcClient::new(GrpcConfig {
///     endpoint: "http://localhost:8000".into(),
///     connector: "stripe".into(),
///     connector_config: build_connector_config("Stripe", ConnectorSpecificConfig::new("sk_test_...")),
/// }).await?;
///
{% for service in services[:4] %}
/// let _ = client.{{ service_to_grpc_field(service) }}.{{ groups[service][0].name }}(Default::default()).await;
{% endfor %}
/// # Ok(()) }
/// ```
pub struct GrpcClient {
{% for service in services %}
    pub {{ service_to_grpc_field(service) }}: {{ service_to_grpc_struct(service) }},
{% endfor %}
}

impl GrpcClient {
    /// Connect to the server eagerly — fails fast if the endpoint is unreachable.
    ///
    /// # Errors
    /// Returns [`tonic::transport::Error`] if the URI is invalid or the TCP
    /// connection cannot be established.
    pub async fn new(config: GrpcConfig) -> Result<Self, tonic::transport::Error> {
        let endpoint = config.endpoint.clone();
        let headers = Arc::new(config.into_headers());

        let channel = Channel::from_shared(endpoint)
            .expect("invalid endpoint URI")
            .connect()
            .await?;

        Ok(Self {
{% for service in services %}
            {{ service_to_grpc_field(service) }}: {{ service_to_grpc_struct(service) }}::new(channel.clone(), Arc::clone(&headers)),
{% endfor %}
        })
    }
}
