//! Tests for el-services.

#[cfg(test)]
mod tests {
    use std::collections::HashMap;
    use std::sync::Arc;

    use crate::{
        binding::{
            direct::DirectBinding,
            grpc::GrpcBinding,
            rest::RestBinding,
            websocket::WebSocketBinding,
            Binding, BindingKind, ServiceRequest, ServiceResponse,
        },
        config::{AuthConfig, ServiceConfig},
        proxy::{ServiceMethod, ServiceProxy},
        registry::ServiceRegistry,
    };

    fn rest_config(base_url: &str) -> ServiceConfig {
        ServiceConfig::new("UserService", BindingKind::Rest)
            .with_base_url(base_url)
    }

    // ── Test 1: BindingKind::from_str parses all kinds ───────────────────────
    #[test]
    fn test_binding_kind_from_str() {
        assert_eq!(BindingKind::from_str("rest"), Some(BindingKind::Rest));
        assert_eq!(BindingKind::from_str("websocket"), Some(BindingKind::WebSocket));
        assert_eq!(BindingKind::from_str("ws"), Some(BindingKind::WebSocket));
        assert_eq!(BindingKind::from_str("grpc"), Some(BindingKind::Grpc));
        assert_eq!(BindingKind::from_str("direct"), Some(BindingKind::Direct));
        assert_eq!(BindingKind::from_str("unknown"), None);
    }

    // ── Test 2: RestBinding derives correct HTTP methods ─────────────────────
    #[test]
    fn test_rest_http_method_derivation() {
        assert_eq!(RestBinding::http_method("get_user"), "GET");
        assert_eq!(RestBinding::http_method("list_users"), "GET");
        assert_eq!(RestBinding::http_method("create_user"), "POST");
        assert_eq!(RestBinding::http_method("update_user"), "PUT");
        assert_eq!(RestBinding::http_method("delete_user"), "DELETE");
        assert_eq!(RestBinding::http_method("patch_user"), "PATCH");
        assert_eq!(RestBinding::http_method("process"), "POST");
    }

    // ── Test 3: RestBinding derives correct URL path ──────────────────────────
    #[test]
    fn test_rest_url_path() {
        let config = rest_config("https://api.example.com");
        let binding = RestBinding::new(config);
        let mut params = HashMap::new();
        params.insert("id".to_string(), "123".to_string());
        let url = binding.url_path("get_user", &params);
        assert!(url.contains("users"), "should use pluralized resource");
        assert!(url.contains("123"), "should include id in URL");
    }

    // ── Test 4: RestBinding call returns ok response ──────────────────────────
    #[test]
    fn test_rest_binding_call() {
        let config = rest_config("https://api.example.com");
        let binding = RestBinding::new(config);
        let request = ServiceRequest::new("UserService", "get_user")
            .with_param("id", "42");
        let response = binding.call(request).unwrap();
        assert!(response.is_success());
        assert!(response.body.contains("UserService"));
    }

    // ── Test 5: RestBinding without base_url returns error ───────────────────
    #[test]
    fn test_rest_binding_no_base_url_errors() {
        let config = ServiceConfig::new("UserService", BindingKind::Rest);
        let binding = RestBinding::new(config);
        let request = ServiceRequest::new("UserService", "get_user");
        let result = binding.call(request);
        assert!(result.is_err(), "should error without base_url");
    }

    // ── Test 6: DirectBinding registers and calls handlers ───────────────────
    #[test]
    fn test_direct_binding_register_and_call() {
        let binding = DirectBinding::new();
        binding.register("UserService", "get_user", |params| {
            let id = params.get("id").cloned().unwrap_or_default();
            Ok(format!("{{\"id\":\"{}\",\"name\":\"Alice\"}}", id))
        });

        let request = ServiceRequest::new("UserService", "get_user")
            .with_param("id", "42");
        let response = binding.call(request).unwrap();
        assert!(response.is_success());
        assert!(response.body.contains("Alice"));
        assert!(response.body.contains("42"));
    }

    // ── Test 7: DirectBinding returns error for missing handler ──────────────
    #[test]
    fn test_direct_binding_missing_handler() {
        let binding = DirectBinding::new();
        let request = ServiceRequest::new("UserService", "nonexistent");
        let result = binding.call(request);
        assert!(result.is_err(), "should error on missing handler");
    }

    // ── Test 8: DirectBinding::has_handler works ─────────────────────────────
    #[test]
    fn test_direct_binding_has_handler() {
        let binding = DirectBinding::new();
        assert!(!binding.has_handler("UserService", "get_user"));
        binding.register("UserService", "get_user", |_| Ok("{}".into()));
        assert!(binding.has_handler("UserService", "get_user"));
    }

    // ── Test 9: GrpcBinding builds correct endpoint path ────────────────────
    #[test]
    fn test_grpc_endpoint_path() {
        let config = ServiceConfig::new("UserService", BindingKind::Grpc)
            .with_base_url("http://localhost:50051");
        let binding = GrpcBinding::new(config).with_package("myapp.v1");
        let endpoint = binding.grpc_endpoint("get_user");
        assert_eq!(endpoint, "/myapp.v1.UserService/GetUser");
    }

    // ── Test 10: GrpcBinding without package ────────────────────────────────
    #[test]
    fn test_grpc_endpoint_no_package() {
        let config = ServiceConfig::new("OrderService", BindingKind::Grpc)
            .with_base_url("http://localhost:50051");
        let binding = GrpcBinding::new(config);
        let endpoint = binding.grpc_endpoint("list_orders");
        assert_eq!(endpoint, "/OrderService/ListOrders");
    }

    // ── Test 11: WebSocketBinding converts URL protocol ──────────────────────
    #[test]
    fn test_websocket_url_conversion() {
        let config = ServiceConfig::new("ChatService", BindingKind::WebSocket)
            .with_base_url("https://ws.example.com");
        let binding = WebSocketBinding::new(config);
        let ws_url = binding.ws_url().unwrap();
        assert!(ws_url.starts_with("wss://"), "should convert https to wss");
    }

    // ── Test 12: WebSocketBinding serializes request correctly ───────────────
    #[test]
    fn test_websocket_request_serialization() {
        let config = ServiceConfig::new("ChatService", BindingKind::WebSocket)
            .with_base_url("wss://ws.example.com");
        let binding = WebSocketBinding::new(config);
        let request = ServiceRequest::new("ChatService", "send_message")
            .with_param("content", "hello");
        let msg = binding.serialize_request(&request, "test-id-123");
        assert!(msg.contains("test-id-123"));
        assert!(msg.contains("ChatService"));
        assert!(msg.contains("send_message"));
        assert!(msg.contains("hello"));
    }

    // ── Test 13: ServiceProxy::call_positional maps args to params ───────────
    #[test]
    fn test_service_proxy_positional_call() {
        let binding = DirectBinding::new();
        binding.register("UserService", "create_user", |params| {
            let name = params.get("name").cloned().unwrap_or_default();
            let email = params.get("email").cloned().unwrap_or_default();
            Ok(format!("{{\"name\":\"{}\",\"email\":\"{}\"}}", name, email))
        });

        let config = ServiceConfig::new("UserService", BindingKind::Direct);
        let method = ServiceMethod::new("create_user", "User")
            .with_params(vec!["name", "email"]);
        let proxy = ServiceProxy::new("UserService", config, Arc::new(binding))
            .with_method(method);

        let response = proxy
            .call_positional("create_user", vec!["Alice".into(), "alice@example.com".into()])
            .unwrap();
        assert!(response.body.contains("Alice"));
        assert!(response.body.contains("alice@example.com"));
    }

    // ── Test 14: ServiceRegistry stores and retrieves services ───────────────
    #[test]
    fn test_service_registry() {
        let registry = ServiceRegistry::new();
        let config = ServiceConfig::new("UserService", BindingKind::Direct);
        let proxy = ServiceProxy::new("UserService", config, Arc::new(DirectBinding::new()));
        registry.register(proxy);

        let retrieved = registry.get("UserService").unwrap();
        assert_eq!(retrieved.service_name, "UserService");
    }

    // ── Test 15: ServiceRegistry returns error for unknown service ────────────
    #[test]
    fn test_service_registry_not_found() {
        let registry = ServiceRegistry::new();
        let result = registry.get("NonExistentService");
        assert!(result.is_err());
    }

    // ── Test 16: ServiceConfig with_auth sets bearer token ───────────────────
    #[test]
    fn test_service_config_with_auth() {
        let config = ServiceConfig::new("UserService", BindingKind::Rest)
            .with_base_url("https://api.example.com")
            .with_auth(AuthConfig::Bearer { token_env: "API_TOKEN".into() });
        match &config.auth {
            AuthConfig::Bearer { token_env } => assert_eq!(token_env, "API_TOKEN"),
            _ => panic!("expected Bearer auth"),
        }
    }

    // ── Test 17: ServiceResponse::is_success ─────────────────────────────────
    #[test]
    fn test_service_response_success() {
        assert!(ServiceResponse::ok("{}").is_success());
        assert!(!ServiceResponse::error(404, "not found").is_success());
        assert!(!ServiceResponse::error(500, "error").is_success());
    }

    // ── Test 18: WebSocketBinding supports streaming ─────────────────────────
    #[test]
    fn test_websocket_supports_streaming() {
        let config = ServiceConfig::new("ChatService", BindingKind::WebSocket)
            .with_base_url("wss://ws.example.com");
        let binding = WebSocketBinding::new(config);
        assert!(binding.supports_streaming());
    }

    // ── Test 19: RestBinding::build_request_description includes method ───────
    #[test]
    fn test_rest_request_description() {
        let config = rest_config("https://api.example.com");
        let binding = RestBinding::new(config);
        let request = ServiceRequest::new("UserService", "create_user")
            .with_param("name", "Alice");
        let desc = binding.build_request_description(&request);
        assert!(desc.contains("POST"), "create_ maps to POST");
        assert!(desc.contains("users"), "resource name derived from service");
    }

    // ── Test 20: ServiceRegistry lists all service names ─────────────────────
    #[test]
    fn test_service_registry_list() {
        let registry = ServiceRegistry::new();
        for name in ["UserService", "OrderService", "ProductService"] {
            let config = ServiceConfig::new(name, BindingKind::Direct);
            let proxy = ServiceProxy::new(name, config, Arc::new(DirectBinding::new()));
            registry.register(proxy);
        }
        let mut names = registry.service_names();
        names.sort();
        assert_eq!(names, vec!["OrderService", "ProductService", "UserService"]);
        assert_eq!(registry.len(), 3);
    }
}
