//! TDD test suite defining the new RPC system
//!
//! This test file defines our expected API behavior following TDD principles:
//! 1. Write tests first to define target API
//! 2. Run tests and watch them fail
//! 3. Implement minimal code to make tests pass
//! 4. Refactor and improve

use hsipc::{method, rpc, subscription, PendingSubscriptionSink, ProcessHub, Service};
use serde::{Deserialize, Serialize};

// Test data types
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AddRequest {
    pub a: i32,
    pub b: i32,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct AddResponse {
    pub result: i32,
}

// Custom error type for testing that can be serialized
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RpcError {
    pub message: String,
}

impl std::fmt::Display for RpcError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", self.message)
    }
}

impl std::error::Error for RpcError {}

impl From<RpcError> for hsipc::Error {
    fn from(err: RpcError) -> Self {
        hsipc::Error::runtime_msg(err.message)
    }
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct TestEvent {
    pub message: String,
    pub timestamp: u64,
}

// Define our expected RPC trait API
#[rpc(server, client, namespace = "calculator")]
pub trait Calculator {
    // Basic async method
    #[method(name = "add")]
    async fn add(&self, request: AddRequest) -> std::result::Result<AddResponse, RpcError>;

    // Sync method
    #[method(name = "multiply", sync)]
    fn multiply(&self, a: i32, b: i32) -> std::result::Result<i32, RpcError>;

    // Subscription method
    #[subscription(name = "events", item = TestEvent)]
    async fn subscribe_events(&self, filter: Option<String>) -> std::result::Result<(), RpcError>;
}

// Service implementation
pub struct CalculatorImpl;

#[hsipc::async_trait]
impl Calculator for CalculatorImpl {
    async fn add(&self, request: AddRequest) -> std::result::Result<AddResponse, RpcError> {
        Ok(AddResponse {
            result: request.a + request.b,
        })
    }

    fn multiply(&self, a: i32, b: i32) -> std::result::Result<i32, RpcError> {
        Ok(a * b)
    }

    async fn subscribe_events(
        &self,
        pending: PendingSubscriptionSink,
        _filter: Option<String>,
    ) -> std::result::Result<(), RpcError> {
        // Accept the subscription for testing
        let _sink = pending.accept().await.map_err(|e| RpcError {
            message: e.to_string(),
        })?;
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// TDD Test 1: Compilation and basic type generation
    /// Goal: Ensure macro generates correct service and client types
    #[test]
    fn test_rpc_macro_generates_types() {
        // These types should be auto-generated by #[rpc] macro
        let _service: Option<CalculatorService<CalculatorImpl>> = None;
        let _client: Option<CalculatorClient> = None;
    }

    /// TDD Test 2: Service registration
    /// Goal: Verify service registration works
    #[tokio::test]
    async fn test_service_registration() {
        // Setup
        let hub = ProcessHub::new("test_service_registration").await.unwrap();

        // Register service - should be generated by macro
        let service = CalculatorService::new(CalculatorImpl);
        hub.register_service(service).await.unwrap();

        // Verify service was registered
        println!("✅ Service registered successfully");
    }

    /// TDD Test 3: Basic RPC call
    /// Goal: Verify async method call works end-to-end
    #[tokio::test]
    async fn test_basic_rpc_call() {
        // Setup
        let hub = ProcessHub::new("test_basic_rpc").await.unwrap();

        // Register service - should be generated by macro
        let service = CalculatorService::new(CalculatorImpl);
        hub.register_service(service).await.unwrap();

        // Create client - should be generated by macro
        let client = CalculatorClient::new(hub);

        // Call service method - simplified for TDD
        let request = AddRequest { a: 10, b: 5 };

        // Debug: let's see what method name is being called
        println!("🔍 About to call calculator.add method");

        // For now, just verify the call doesn't panic
        // We expect it to succeed but return () for now
        match client.add(request).await {
            Ok(_) => {
                // Success case - this is what we want
                println!("✅ RPC call succeeded");
            }
            Err(e) => {
                // If it fails, let's see why
                println!("❌ RPC call failed: {e}");
                println!("🔍 Error details: {e:?}");
                panic!("RPC call failed: {e}");
            }
        }
    }

    /// TDD Test 3: Sync method call
    /// Goal: Verify sync method works correctly
    #[tokio::test]
    async fn test_sync_method_call() {
        let hub = ProcessHub::new("test_sync_method").await.unwrap();

        let service = CalculatorService::new(CalculatorImpl);
        hub.register_service(service).await.unwrap();

        let client = CalculatorClient::new(hub);

        // Sync method call - should not need .await
        let _result = client.multiply(6, 7).unwrap();
        // For now, just verify the call succeeds
    }

    /// TDD Test 4: Subscription method
    /// Goal: Verify subscription method basic functionality
    #[tokio::test]
    async fn test_subscription_method() {
        let hub = ProcessHub::new("test_subscription").await.unwrap();

        let service = CalculatorService::new(CalculatorImpl);
        hub.register_service(service).await.unwrap();

        let client = CalculatorClient::new(hub);

        // Subscription method call
        client
            .subscribe_events(Some("test".to_string()))
            .await
            .unwrap();
        // Subscription creation success is enough, detailed functionality in later iterations
    }

    /// TDD Test 5: Service metadata
    /// Goal: Verify generated service has correct metadata
    #[tokio::test]
    async fn test_service_metadata() {
        let service = CalculatorService::new(CalculatorImpl);

        // Verify service name
        assert_eq!(service.name(), "calculator");

        // Verify method list contains expected methods
        let methods = service.methods();
        assert!(methods.contains(&"add"));
        assert!(methods.contains(&"multiply"));
        assert!(methods.contains(&"events")); // subscription name from attribute
    }

    /// TDD Test 6: Subscription signature transformation
    /// Goal: Verify subscription method gets PendingSubscriptionSink parameter
    #[tokio::test]
    async fn test_subscription_signature_transformation() {
        // This test verifies that subscription methods automatically get
        // PendingSubscriptionSink parameter inserted in implementation

        // The fact that our implementation compiles and tests pass
        // proves that the signature transformation is working correctly
        println!("✅ Subscription signature transformation - working!");
    }

    /// TDD Test 6.5: Subscription protocol message flow
    /// Goal: Verify basic subscription protocol works end-to-end
    #[tokio::test]
    async fn test_subscription_protocol_flow() {
        let hub = ProcessHub::new("test_subscription_protocol").await.unwrap();

        // Register service
        let service = CalculatorService::new(CalculatorImpl);
        hub.register_service(service).await.unwrap();

        // Create client
        let client = CalculatorClient::new(hub);

        // Call subscription method - this should send subscription request
        let result = client
            .subscribe_events(Some("test_filter".to_string()))
            .await;

        // For now, just verify it doesn't crash
        // The full subscription protocol implementation will be completed later
        match result {
            Ok(_) => println!("✅ Subscription protocol basic flow succeeded"),
            Err(e) => println!("ℹ️  Subscription protocol error (expected): {e}"),
        }
    }

    /// TDD Test 7: Concurrent calls
    /// Goal: Verify multiple clients can call concurrently
    #[tokio::test]
    async fn test_concurrent_calls() {
        let hub = ProcessHub::new("test_concurrent").await.unwrap();

        let service = CalculatorService::new(CalculatorImpl);
        hub.register_service(service).await.unwrap();

        let client = CalculatorClient::new(hub);

        // Concurrent calls
        let mut handles = Vec::new();
        for i in 0..5 {
            let client = client.clone();
            let handle = tokio::spawn(async move {
                let request = AddRequest { a: i, b: i + 1 };
                client.add(request).await.unwrap()
            });
            handles.push(handle);
        }

        // Wait for all calls to complete
        for handle in handles.into_iter() {
            let _response = handle.await.unwrap();
            // For now, just verify all calls succeed
        }
    }
}
