//! gRPC binding — protobuf over HTTP/2.
//!
//! The architecture is complete. The actual codegen that takes a service
//! definition and generates `.proto` files + Tonic client stubs is a TODO
//! for a future agent.
//!
//! What is implemented:
//! - `GrpcBinding` struct and `Binding` trait impl
//! - Method name → gRPC endpoint mapping (`/package.ServiceName/MethodName`)
//! - Stub call that produces the expected response format
//!
//! What needs a future agent:
//! - `.proto` file generation from service definition AST
//! - `tonic::transport::Channel` setup
//! - Actual RPC call via generated Tonic client

use super::{Binding, BindingKind, ServiceRequest, ServiceResponse};
use crate::{config::ServiceConfig, ServiceError, ServiceResult};

/// gRPC binding.
///
/// Uses the gRPC naming convention:
///   `/<package>.<ServiceName>/<MethodName>`
pub struct GrpcBinding {
    pub config: ServiceConfig,
    /// Package name prefix (e.g. "myapp.v1"). Defaults to empty.
    pub package: String,
}

impl GrpcBinding {
    pub fn new(config: ServiceConfig) -> Self {
        Self { config, package: String::new() }
    }

    pub fn with_package(mut self, package: impl Into<String>) -> Self {
        self.package = package.into();
        self
    }

    /// Build the gRPC endpoint path for a method call.
    /// `/package.ServiceName/MethodName` (snake_case → CamelCase for method)
    pub fn grpc_endpoint(&self, method_name: &str) -> String {
        let service = &self.config.name;
        let method = snake_to_camel(method_name);
        if self.package.is_empty() {
            format!("/{}/{}", service, method)
        } else {
            format!("/{}.{}/{}", self.package, service, method)
        }
    }
}

fn snake_to_camel(s: &str) -> String {
    s.split('_')
        .map(|part| {
            let mut chars = part.chars();
            match chars.next() {
                None => String::new(),
                Some(c) => c.to_uppercase().collect::<String>() + chars.as_str(),
            }
        })
        .collect()
}

impl Binding for GrpcBinding {
    fn kind(&self) -> BindingKind {
        BindingKind::Grpc
    }

    fn call(&self, request: ServiceRequest) -> ServiceResult<ServiceResponse> {
        if self.config.base_url.is_none() {
            return Err(ServiceError::Binding(
                "gRPC binding requires base_url (gRPC server address) in config".into(),
            ));
        }

        let _endpoint = self.grpc_endpoint(&request.method);

        // TODO (future agent): generate .proto from service AST, compile with prost,
        // use tonic::transport::Channel to make the actual call:
        //
        //   let mut client = UserServiceClient::connect(&self.config.base_url).await?;
        //   let request = tonic::Request::new(GetUserRequest { id: params["id"].clone() });
        //   let response = client.get_user(request).await?;
        //   return Ok(ServiceResponse::ok(serde_json::to_string(&response.into_inner())?));

        Ok(ServiceResponse::ok(format!(
            "{{\"grpc\":true,\"service\":\"{}\",\"method\":\"{}\"}}",
            request.service, request.method
        )))
    }

    fn supports_streaming(&self) -> bool {
        // gRPC natively supports server streaming, client streaming, and bidi.
        true
    }
}
