//! Unit tests for k8shost services
//!
//! These tests verify the basic functionality of Pod, Service, and Node CRUD operations.
//! Note: These tests require a running FlareDB instance for full integration testing.

#[cfg(test)]
mod tests {
    use crate::auth::AuthService;
    use crate::services::{
        node::NodeServiceImpl, pod::PodServiceImpl, service::ServiceServiceImpl,
    };
    use crate::storage::Storage;
    use iam_service_auth::TenantContext;
    use iam_types::PrincipalKind;
    use k8shost_proto::{
        node_service_server::NodeService, pod_service_server::PodService,
        service_service_server::ServiceService, *,
    };
    use std::collections::HashMap;
    use std::sync::Arc;
    use tonic::Request;

    fn with_test_tenant<T>(mut request: Request<T>) -> Request<T> {
        request.extensions_mut().insert(TenantContext {
            org_id: "test-org".to_string(),
            project_id: "test-project".to_string(),
            principal_id: "test-user".to_string(),
            principal_name: "test-user".to_string(),
            principal_kind: PrincipalKind::User,
            node_id: None,
        });
        request
    }

    async fn test_auth_service() -> Arc<AuthService> {
        let iam_addr = std::env::var("IAM_ADDR")
            .or_else(|_| std::env::var("PHOTON_IAM_ENDPOINT"))
            .unwrap_or_else(|_| "http://127.0.0.1:50080".to_string());
        Arc::new(
            AuthService::new(&iam_addr)
                .await
                .expect("Failed to connect to IAM"),
        )
    }

    // Helper function to create a test pod
    fn create_test_pod(name: &str, namespace: &str) -> Pod {
        Pod {
            metadata: Some(ObjectMeta {
                name: name.to_string(),
                namespace: Some(namespace.to_string()),
                uid: None,
                resource_version: None,
                creation_timestamp: None,
                labels: HashMap::from([("app".to_string(), "test".to_string())]),
                annotations: HashMap::new(),
                org_id: Some("test-org".to_string()),
                project_id: Some("test-project".to_string()),
            }),
            spec: Some(PodSpec {
                containers: vec![Container {
                    name: "nginx".to_string(),
                    image: "nginx:latest".to_string(),
                    command: vec![],
                    args: vec![],
                    ports: vec![ContainerPort {
                        name: Some("http".to_string()),
                        container_port: 80,
                        protocol: Some("TCP".to_string()),
                    }],
                    env: vec![],
                }],
                restart_policy: Some("Always".to_string()),
                node_name: None,
            }),
            status: None,
        }
    }

    // Helper function to create a test service
    fn create_test_service(name: &str, namespace: &str) -> Service {
        Service {
            metadata: Some(ObjectMeta {
                name: name.to_string(),
                namespace: Some(namespace.to_string()),
                uid: None,
                resource_version: None,
                creation_timestamp: None,
                labels: HashMap::new(),
                annotations: HashMap::new(),
                org_id: Some("test-org".to_string()),
                project_id: Some("test-project".to_string()),
            }),
            spec: Some(ServiceSpec {
                ports: vec![ServicePort {
                    name: Some("http".to_string()),
                    port: 80,
                    target_port: Some(8080),
                    protocol: Some("TCP".to_string()),
                }],
                selector: HashMap::from([("app".to_string(), "test".to_string())]),
                cluster_ip: None,
                r#type: Some("ClusterIP".to_string()),
            }),
            status: None,
        }
    }

    // Helper function to create a test node
    fn create_test_node(name: &str) -> Node {
        Node {
            metadata: Some(ObjectMeta {
                name: name.to_string(),
                namespace: None,
                uid: None,
                resource_version: None,
                creation_timestamp: None,
                labels: HashMap::new(),
                annotations: HashMap::new(),
                org_id: Some("test-org".to_string()),
                project_id: Some("test-project".to_string()),
            }),
            spec: Some(NodeSpec {
                pod_cidr: Some("10.244.0.0/24".to_string()),
            }),
            status: Some(NodeStatus {
                addresses: vec![NodeAddress {
                    r#type: "InternalIP".to_string(),
                    address: "192.168.1.100".to_string(),
                }],
                conditions: vec![NodeCondition {
                    r#type: "Ready".to_string(),
                    status: "True".to_string(),
                    reason: None,
                    message: None,
                }],
                capacity: HashMap::from([
                    ("cpu".to_string(), "4".to_string()),
                    ("memory".to_string(), "8Gi".to_string()),
                ]),
                allocatable: HashMap::from([
                    ("cpu".to_string(), "3.5".to_string()),
                    ("memory".to_string(), "7Gi".to_string()),
                ]),
            }),
        }
    }

    #[test]
    fn test_pod_proto_conversion() {
        // Test that we can convert between proto and internal types
        let proto_pod = create_test_pod("test-pod", "default");
        let result = PodServiceImpl::from_proto_pod(&proto_pod);
        assert!(result.is_ok());

        let internal_pod = result.unwrap();
        assert_eq!(internal_pod.metadata.name, "test-pod");
        assert_eq!(internal_pod.metadata.namespace, Some("default".to_string()));
        assert_eq!(internal_pod.spec.containers.len(), 1);
        assert_eq!(internal_pod.spec.containers[0].name, "nginx");

        // Convert back to proto
        let proto_pod2 = PodServiceImpl::to_proto_pod(&internal_pod);
        assert_eq!(proto_pod2.metadata.as_ref().unwrap().name, "test-pod");
    }

    #[test]
    fn test_service_proto_conversion() {
        let proto_service = create_test_service("test-service", "default");
        let result = ServiceServiceImpl::from_proto_service(&proto_service);
        assert!(result.is_ok());

        let internal_service = result.unwrap();
        assert_eq!(internal_service.metadata.name, "test-service");
        assert_eq!(internal_service.spec.ports.len(), 1);
        assert_eq!(internal_service.spec.ports[0].port, 80);

        // Convert back to proto
        let proto_service2 = ServiceServiceImpl::to_proto_service(&internal_service);
        assert_eq!(
            proto_service2.metadata.as_ref().unwrap().name,
            "test-service"
        );
    }

    #[test]
    fn test_node_proto_conversion() {
        let proto_node = create_test_node("test-node");
        let result = NodeServiceImpl::from_proto_node(&proto_node);
        assert!(result.is_ok());

        let internal_node = result.unwrap();
        assert_eq!(internal_node.metadata.name, "test-node");
        assert!(internal_node.status.is_some());
        assert_eq!(internal_node.status.as_ref().unwrap().addresses.len(), 1);

        // Convert back to proto
        let proto_node2 = NodeServiceImpl::to_proto_node(&internal_node);
        assert_eq!(proto_node2.metadata.as_ref().unwrap().name, "test-node");
    }

    // Integration tests that require FlareDB
    // These are disabled by default and can be enabled when FlareDB is available

    #[tokio::test]
    #[ignore] // Requires running FlareDB instance
    async fn test_pod_crud_operations() {
        // This test requires a running FlareDB instance
        let pd_addr = std::env::var("FLAREDB_PD_ADDR").unwrap_or("127.0.0.1:2479".to_string());
        let storage = Storage::new(pd_addr)
            .await
            .expect("Failed to connect to FlareDB");
        let pod_service = PodServiceImpl::new(Arc::new(storage), test_auth_service().await);

        // Create a pod
        let pod = create_test_pod("test-pod-1", "default");
        let create_req = with_test_tenant(Request::new(CreatePodRequest { pod: Some(pod) }));
        let create_resp = pod_service.create_pod(create_req).await;
        assert!(create_resp.is_ok());
        let created_pod = create_resp.unwrap().into_inner().pod.unwrap();
        assert!(created_pod.metadata.as_ref().unwrap().uid.is_some());

        // Get the pod
        let get_req = with_test_tenant(Request::new(GetPodRequest {
            namespace: "default".to_string(),
            name: "test-pod-1".to_string(),
        }));
        let get_resp = pod_service.get_pod(get_req).await;
        assert!(get_resp.is_ok());
        let retrieved_pod = get_resp.unwrap().into_inner().pod.unwrap();
        assert_eq!(retrieved_pod.metadata.as_ref().unwrap().name, "test-pod-1");

        // List pods
        let list_req = with_test_tenant(Request::new(ListPodsRequest {
            namespace: Some("default".to_string()),
            label_selector: HashMap::new(),
        }));
        let list_resp = pod_service.list_pods(list_req).await;
        assert!(list_resp.is_ok());
        let pods = list_resp.unwrap().into_inner().items;
        assert!(pods.len() >= 1);

        // Delete the pod
        let delete_req = with_test_tenant(Request::new(DeletePodRequest {
            namespace: "default".to_string(),
            name: "test-pod-1".to_string(),
        }));
        let delete_resp = pod_service.delete_pod(delete_req).await;
        assert!(delete_resp.is_ok());
        assert!(delete_resp.unwrap().into_inner().success);
    }

    #[tokio::test]
    #[ignore] // Requires running FlareDB and PrismNET instances
    async fn test_service_crud_operations() {
        let pd_addr = std::env::var("FLAREDB_PD_ADDR").unwrap_or("127.0.0.1:2479".to_string());
        let storage = Storage::new(pd_addr)
            .await
            .expect("Failed to connect to FlareDB");
        let prismnet_addr =
            std::env::var("PRISMNET_ADDR").unwrap_or("http://127.0.0.1:9090".to_string());
        let ipam_client = crate::ipam_client::IpamClient::new(prismnet_addr);
        let service_service = ServiceServiceImpl::new(
            Arc::new(storage),
            Arc::new(ipam_client),
            test_auth_service().await,
        );

        // Create a service
        let service = create_test_service("test-service-1", "default");
        let create_req = with_test_tenant(Request::new(CreateServiceRequest {
            service: Some(service),
        }));
        let create_resp = service_service.create_service(create_req).await;
        assert!(create_resp.is_ok());
        let created_service = create_resp.unwrap().into_inner().service.unwrap();
        assert!(created_service.spec.as_ref().unwrap().cluster_ip.is_some());

        // Get the service
        let get_req = with_test_tenant(Request::new(GetServiceRequest {
            namespace: "default".to_string(),
            name: "test-service-1".to_string(),
        }));
        let get_resp = service_service.get_service(get_req).await;
        assert!(get_resp.is_ok());

        // List services
        let list_req = with_test_tenant(Request::new(ListServicesRequest {
            namespace: Some("default".to_string()),
        }));
        let list_resp = service_service.list_services(list_req).await;
        assert!(list_resp.is_ok());

        // Delete the service
        let delete_req = with_test_tenant(Request::new(DeleteServiceRequest {
            namespace: "default".to_string(),
            name: "test-service-1".to_string(),
        }));
        let delete_resp = service_service.delete_service(delete_req).await;
        assert!(delete_resp.is_ok());
    }

    #[tokio::test]
    #[ignore] // Requires running FlareDB instance
    async fn test_node_operations() {
        let pd_addr = std::env::var("FLAREDB_PD_ADDR").unwrap_or("127.0.0.1:2479".to_string());
        let storage = Storage::new(pd_addr)
            .await
            .expect("Failed to connect to FlareDB");
        let node_service = NodeServiceImpl::new(Arc::new(storage), test_auth_service().await);

        // Register a node
        let node = create_test_node("test-node-1");
        let register_req = with_test_tenant(Request::new(RegisterNodeRequest { node: Some(node) }));
        let register_resp = node_service.register_node(register_req).await;
        assert!(register_resp.is_ok());

        // Send heartbeat
        let heartbeat_req = with_test_tenant(Request::new(HeartbeatRequest {
            node_name: "test-node-1".to_string(),
            status: Some(NodeStatus {
                addresses: vec![],
                conditions: vec![NodeCondition {
                    r#type: "Ready".to_string(),
                    status: "True".to_string(),
                    reason: None,
                    message: None,
                }],
                capacity: HashMap::new(),
                allocatable: HashMap::new(),
            }),
        }));
        let heartbeat_resp = node_service.heartbeat(heartbeat_req).await;
        assert!(heartbeat_resp.is_ok());
        assert!(heartbeat_resp.unwrap().into_inner().success);

        // List nodes
        let list_req = with_test_tenant(Request::new(ListNodesRequest {}));
        let list_resp = node_service.list_nodes(list_req).await;
        assert!(list_resp.is_ok());
        let nodes = list_resp.unwrap().into_inner().items;
        assert!(nodes.len() >= 1);
    }
}
