"""
Integration tests for Kubernetes deployment of OpenTelemetry Collector.

These tests verify the high-availability configuration including:
- StatefulSet deployment with 3 replicas
- Persistent storage provisioning
- Health check functionality
- Load balancing across instances
- Failover scenarios

Prerequisites:
- kubectl configured with access to test cluster
- Namespace 'monitoring' exists
- All k8s manifests deployed
"""

import json
import subprocess
import time
from typing import Dict, List, Optional

import pytest
import requests


class KubernetesClient:
    """Helper class for kubectl operations."""

    def __init__(self, namespace: str = "monitoring"):
        self.namespace = namespace

    def run_kubectl(self, *args: str) -> str:
        """Execute kubectl command and return output."""
        cmd = ["kubectl", "-n", self.namespace] + list(args)
        result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
        return result.stdout.strip()

    def get_pods(self, label_selector: str) -> List[Dict]:
        """Get pods matching label selector."""
        output = self.run_kubectl("get", "pods", "-l", label_selector, "-o", "json")
        return json.loads(output).get("items", [])

    def get_pod_status(self, pod_name: str) -> str:
        """Get pod status."""
        output = self.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        return pod["status"]["phase"]

    def get_service(self, service_name: str) -> Dict:
        """Get service details."""
        output = self.run_kubectl("get", "svc", service_name, "-o", "json")
        return json.loads(output)

    def get_pvc(self, pvc_name: str) -> Dict:
        """Get PVC details."""
        output = self.run_kubectl("get", "pvc", pvc_name, "-o", "json")
        return json.loads(output)

    def delete_pod(self, pod_name: str) -> None:
        """Delete a pod."""
        self.run_kubectl("delete", "pod", pod_name, "--wait=false")

    def exec_in_pod(self, pod_name: str, command: List[str]) -> str:
        """Execute command in pod."""
        cmd = ["kubectl", "-n", self.namespace, "exec", pod_name, "--"] + command
        result = subprocess.run(cmd, capture_output=True, text=True, check=True, timeout=30)
        return result.stdout.strip()

    def port_forward(self, pod_name: str, local_port: int, remote_port: int):
        """Start port forwarding (returns subprocess.Popen)."""
        cmd = [
            "kubectl",
            "-n",
            self.namespace,
            "port-forward",
            pod_name,
            f"{local_port}:{remote_port}",
        ]
        return subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)


@pytest.fixture(scope="module")
def k8s_client():
    """Kubernetes client fixture."""
    return KubernetesClient()


@pytest.fixture(scope="module")
def collector_pods(k8s_client):
    """Get collector pod names."""
    pods = k8s_client.get_pods("app=otel-collector")
    assert len(pods) >= 3, f"Expected at least 3 collector pods, found {len(pods)}"
    return [pod["metadata"]["name"] for pod in pods]


class TestStatefulSetDeployment:
    """Test StatefulSet deployment configuration."""

    def test_statefulset_exists(self, k8s_client):
        """Verify StatefulSet is deployed."""
        output = k8s_client.run_kubectl("get", "statefulset", "otel-collector")
        assert "otel-collector" in output

    def test_three_replicas_running(self, k8s_client, collector_pods):
        """Verify 3 collector replicas are running."""
        assert len(collector_pods) >= 3, "Expected at least 3 collector pods"

        for pod_name in collector_pods[:3]:
            status = k8s_client.get_pod_status(pod_name)
            assert status == "Running", f"Pod {pod_name} is not running: {status}"

    def test_pods_have_stable_names(self, collector_pods):
        """Verify pods have StatefulSet naming pattern."""
        for i, pod_name in enumerate(collector_pods[:3]):
            expected_prefix = f"otel-collector-{i}"
            assert pod_name.startswith(
                expected_prefix
            ), f"Pod {pod_name} doesn't match pattern {expected_prefix}"

    def test_pods_distributed_across_nodes(self, k8s_client, collector_pods):
        """Verify pods are distributed across different nodes (anti-affinity)."""
        nodes = set()
        for pod_name in collector_pods[:3]:
            output = k8s_client.run_kubectl(
                "get", "pod", pod_name, "-o", "jsonpath={.spec.nodeName}"
            )
            nodes.add(output)

        # With 3 pods and anti-affinity, we should have at least 2 different nodes
        assert len(nodes) >= 2, f"Pods not distributed: all on {nodes}"


class TestPersistentStorage:
    """Test persistent storage configuration."""

    def test_storage_class_exists(self, k8s_client):
        """Verify SSD StorageClass exists."""
        output = subprocess.run(
            ["kubectl", "get", "storageclass", "ssd-storage"],
            capture_output=True,
            text=True,
            check=True,
        )
        assert "ssd-storage" in output.stdout

    def test_pvcs_provisioned(self, k8s_client, collector_pods):
        """Verify PVCs are provisioned for each pod."""
        for i in range(3):
            pvc_name = f"queue-storage-otel-collector-{i}"
            pvc = k8s_client.get_pvc(pvc_name)
            assert pvc["status"]["phase"] == "Bound", f"PVC {pvc_name} not bound"

    def test_queue_directory_writable(self, k8s_client, collector_pods):
        """Verify queue directory is writable."""
        pod_name = collector_pods[0]
        # Test write access
        k8s_client.exec_in_pod(pod_name, ["touch", "/var/otel/queue/test-write"])
        # Verify file exists
        output = k8s_client.exec_in_pod(pod_name, ["ls", "/var/otel/queue/test-write"])
        assert "test-write" in output

    def test_storage_size(self, k8s_client, collector_pods):
        """Verify storage size is 10Gi."""
        for i in range(3):
            pvc_name = f"queue-storage-otel-collector-{i}"
            pvc = k8s_client.get_pvc(pvc_name)
            storage = pvc["spec"]["resources"]["requests"]["storage"]
            assert storage == "10Gi", f"PVC {pvc_name} has wrong size: {storage}"


class TestHealthChecks:
    """Test health check configuration."""

    def test_health_check_endpoint_accessible(self, k8s_client, collector_pods):
        """Verify health check endpoint responds."""
        pod_name = collector_pods[0]
        output = k8s_client.exec_in_pod(pod_name, ["curl", "-s", "http://localhost:13133/"])
        response = json.loads(output)
        assert response["status"] == "Server available"

    def test_all_pods_healthy(self, k8s_client, collector_pods):
        """Verify all pods pass health checks."""
        for pod_name in collector_pods[:3]:
            output = k8s_client.exec_in_pod(pod_name, ["curl", "-s", "http://localhost:13133/"])
            response = json.loads(output)
            assert response["status"] == "Server available", f"{pod_name} unhealthy"

    def test_readiness_probe_configured(self, k8s_client, collector_pods):
        """Verify readiness probe is configured."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        container = pod["spec"]["containers"][0]
        assert "readinessProbe" in container
        assert container["readinessProbe"]["httpGet"]["port"] == 13133

    def test_liveness_probe_configured(self, k8s_client, collector_pods):
        """Verify liveness probe is configured."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        container = pod["spec"]["containers"][0]
        assert "livenessProbe" in container
        assert container["livenessProbe"]["httpGet"]["port"] == 13133


class TestServices:
    """Test service configuration."""

    def test_headless_service_exists(self, k8s_client):
        """Verify headless service exists."""
        svc = k8s_client.get_service("otel-collector-headless")
        assert svc["spec"]["clusterIP"] == "None", "Not a headless service"

    def test_cluster_service_exists(self, k8s_client):
        """Verify ClusterIP service exists."""
        svc = k8s_client.get_service("otel-collector")
        assert svc["spec"]["type"] == "ClusterIP"

    def test_external_service_exists(self, k8s_client):
        """Verify external LoadBalancer service exists."""
        svc = k8s_client.get_service("otel-collector-external")
        assert svc["spec"]["type"] == "LoadBalancer"

    def test_service_ports_configured(self, k8s_client):
        """Verify service exposes correct ports."""
        svc = k8s_client.get_service("otel-collector")
        ports = {port["name"]: port["port"] for port in svc["spec"]["ports"]}
        assert ports["otlp-http"] == 4318
        assert ports["otlp-grpc"] == 4317
        assert ports["health"] == 13133

    def test_dns_resolution(self, k8s_client, collector_pods):
        """Verify DNS resolution for headless service."""
        # Test DNS resolution from within a pod
        pod_name = collector_pods[0]
        for i in range(3):
            hostname = f"otel-collector-{i}.otel-collector-headless.monitoring.svc.cluster.local"
            output = k8s_client.exec_in_pod(pod_name, ["nslookup", hostname])
            assert "Name:" in output, f"DNS resolution failed for {hostname}"


class TestTelemetryFlow:
    """Test telemetry data flow through collector."""

    @pytest.fixture(scope="class")
    def port_forward_process(self, k8s_client, collector_pods):
        """Set up port forwarding for testing."""
        pod_name = collector_pods[0]
        process = k8s_client.port_forward(pod_name, 4318, 4318)
        time.sleep(2)  # Wait for port forward to establish
        yield process
        process.terminate()
        process.wait()

    def test_send_metrics_via_http(self, port_forward_process):
        """Test sending metrics via OTLP/HTTP."""
        payload = {
            "resourceMetrics": [
                {
                    "resource": {
                        "attributes": [{"key": "service.name", "value": {"stringValue": "test"}}]
                    },
                    "scopeMetrics": [
                        {
                            "metrics": [
                                {
                                    "name": "test.metric",
                                    "gauge": {"dataPoints": [{"asDouble": 1.0}]},
                                }
                            ]
                        }
                    ],
                }
            ]
        }

        response = requests.post(
            "http://localhost:4318/v1/metrics",
            json=payload,
            headers={"Content-Type": "application/json"},
            timeout=5,
        )
        assert response.status_code == 200

    def test_send_logs_via_http(self, port_forward_process):
        """Test sending logs via OTLP/HTTP."""
        payload = {
            "resourceLogs": [
                {
                    "resource": {
                        "attributes": [{"key": "service.name", "value": {"stringValue": "test"}}]
                    },
                    "scopeLogs": [
                        {
                            "logRecords": [
                                {
                                    "body": {"stringValue": "test log"},
                                    "severityText": "INFO",
                                }
                            ]
                        }
                    ],
                }
            ]
        }

        response = requests.post(
            "http://localhost:4318/v1/logs",
            json=payload,
            headers={"Content-Type": "application/json"},
            timeout=5,
        )
        assert response.status_code == 200

    def test_send_traces_via_http(self, port_forward_process):
        """Test sending traces via OTLP/HTTP."""
        payload = {
            "resourceSpans": [
                {
                    "resource": {
                        "attributes": [{"key": "service.name", "value": {"stringValue": "test"}}]
                    },
                    "scopeSpans": [
                        {
                            "spans": [
                                {
                                    "traceId": "0123456789abcdef0123456789abcdef",
                                    "spanId": "0123456789abcdef",
                                    "name": "test-span",
                                    "kind": 1,
                                    "startTimeUnixNano": "1000000000000000000",
                                    "endTimeUnixNano": "1000000001000000000",
                                }
                            ]
                        }
                    ],
                }
            ]
        }

        response = requests.post(
            "http://localhost:4318/v1/traces",
            json=payload,
            headers={"Content-Type": "application/json"},
            timeout=5,
        )
        assert response.status_code == 200


class TestFailover:
    """Test high-availability failover scenarios."""

    def test_pod_deletion_recovery(self, k8s_client, collector_pods):
        """Test that deleted pod is automatically recreated."""
        pod_name = collector_pods[0]

        # Delete pod
        k8s_client.delete_pod(pod_name)

        # Wait for pod to be recreated
        max_wait = 60
        for _ in range(max_wait):
            try:
                status = k8s_client.get_pod_status(pod_name)
                if status == "Running":
                    break
            except subprocess.CalledProcessError:
                pass
            time.sleep(1)
        else:
            pytest.fail(f"Pod {pod_name} not recreated within {max_wait}s")

        # Verify pod is healthy
        output = k8s_client.exec_in_pod(pod_name, ["curl", "-s", "http://localhost:13133/"])
        response = json.loads(output)
        assert response["status"] == "Server available"

    def test_pdb_prevents_simultaneous_disruption(self, k8s_client):
        """Verify PodDisruptionBudget is configured."""
        output = k8s_client.run_kubectl("get", "pdb", "otel-collector", "-o", "json")
        pdb = json.loads(output)
        assert pdb["spec"]["minAvailable"] == 2, "PDB should require 2 pods available"

    def test_service_continues_during_pod_restart(
        self, k8s_client, collector_pods, port_forward_process
    ):
        """Test that service remains available during pod restart."""
        # Delete one pod
        pod_to_delete = collector_pods[1]  # Don't delete the port-forwarded pod
        k8s_client.delete_pod(pod_to_delete)

        # Verify service still responds
        payload = {
            "resourceMetrics": [
                {
                    "resource": {
                        "attributes": [{"key": "service.name", "value": {"stringValue": "test"}}]
                    },
                    "scopeMetrics": [
                        {
                            "metrics": [
                                {
                                    "name": "test.metric",
                                    "gauge": {"dataPoints": [{"asDouble": 1.0}]},
                                }
                            ]
                        }
                    ],
                }
            ]
        }

        response = requests.post(
            "http://localhost:4318/v1/metrics",
            json=payload,
            headers={"Content-Type": "application/json"},
            timeout=5,
        )
        assert response.status_code == 200, "Service unavailable during pod restart"


class TestSecurity:
    """Test security configuration."""

    def test_pods_run_as_non_root(self, k8s_client, collector_pods):
        """Verify pods run as non-root user."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        security_context = pod["spec"]["securityContext"]
        assert security_context["runAsNonRoot"] is True
        assert security_context["runAsUser"] == 10001

    def test_read_only_root_filesystem(self, k8s_client, collector_pods):
        """Verify container has read-only root filesystem."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        container = pod["spec"]["containers"][0]
        assert container["securityContext"]["readOnlyRootFilesystem"] is True

    def test_no_privilege_escalation(self, k8s_client, collector_pods):
        """Verify privilege escalation is disabled."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        container = pod["spec"]["containers"][0]
        assert container["securityContext"]["allowPrivilegeEscalation"] is False

    def test_capabilities_dropped(self, k8s_client, collector_pods):
        """Verify all capabilities are dropped."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        container = pod["spec"]["containers"][0]
        assert container["securityContext"]["capabilities"]["drop"] == ["ALL"]


class TestResourceManagement:
    """Test resource limits and requests."""

    def test_memory_limits_configured(self, k8s_client, collector_pods):
        """Verify memory limits are set."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        container = pod["spec"]["containers"][0]
        assert container["resources"]["limits"]["memory"] == "768Mi"
        assert container["resources"]["requests"]["memory"] == "512Mi"

    def test_cpu_limits_configured(self, k8s_client, collector_pods):
        """Verify CPU limits are set."""
        pod_name = collector_pods[0]
        output = k8s_client.run_kubectl("get", "pod", pod_name, "-o", "json")
        pod = json.loads(output)
        container = pod["spec"]["containers"][0]
        assert container["resources"]["limits"]["cpu"] == "1"
        assert container["resources"]["requests"]["cpu"] == "500m"


if __name__ == "__main__":
    pytest.main([__file__, "-v", "--tb=short"])
