"""Integration tests for MCA SDK example Docker images.

Tests validate that images:
- Can be built and run successfully
- Run as non-root user (security)
- Have working health checks
- Connect to OTel Collector
- Send telemetry properly
"""

import os
import time
import subprocess
import pytest
import docker
import requests


@pytest.fixture(scope="module")
def docker_client():
    """Docker client for integration tests."""
    return docker.from_env()


@pytest.fixture(scope="module")
def image_registry():
    """Get image registry URL from environment or use local."""
    registry = os.getenv("ARTIFACT_REGISTRY_URL", "")
    return registry if registry else ""


@pytest.fixture(
    scope="module",
    params=[
        "internal-model",
        "internal-genai",
        "internal-agentic",
        "vendor-bridge",
        "registry-genai",
    ],
)
def example_image(request):
    """Parametrized fixture for all 5 SDK example images."""
    return request.param


class TestDockerImageBuild:
    """Test Docker image build process."""

    def test_dockerfile_exists(self, example_image):
        """Verify Dockerfile exists for each example."""
        dockerfile_path = f"sdk-examples/{example_image}/Dockerfile"
        assert os.path.exists(dockerfile_path), f"Dockerfile not found: {dockerfile_path}"

    def test_dockerfile_has_multi_stage(self, example_image):
        """Verify Dockerfile uses multi-stage build."""
        dockerfile_path = f"sdk-examples/{example_image}/Dockerfile"
        with open(dockerfile_path, "r") as f:
            content = f.read()

        assert (
            "FROM python:3.11-slim AS builder" in content
        ), f"{example_image} Dockerfile missing multi-stage build"
        assert (
            "FROM python:3.11-slim" in content.split("AS builder")[1]
        ), f"{example_image} Dockerfile missing runtime stage"

    def test_dockerfile_has_non_root_user(self, example_image):
        """Verify Dockerfile creates and uses non-root user."""
        dockerfile_path = f"sdk-examples/{example_image}/Dockerfile"
        with open(dockerfile_path, "r") as f:
            content = f.read()

        assert "useradd" in content, f"{example_image} Dockerfile missing user creation"
        assert "USER appuser" in content, f"{example_image} Dockerfile missing USER directive"

    def test_dockerfile_has_healthcheck(self, example_image):
        """Verify Dockerfile includes HEALTHCHECK directive for API services only."""
        dockerfile_path = f"sdk-examples/{example_image}/Dockerfile"
        with open(dockerfile_path, "r") as f:
            content = f.read()

        # API services need healthchecks for load balancer probes
        api_services = ["vendor-bridge", "registry-genai"]
        if example_image in api_services:
            assert "HEALTHCHECK" in content, f"{example_image} API service missing HEALTHCHECK"
        else:
            # Batch services don't need healthchecks - Docker monitors PID 1 automatically
            pass

    def test_healthcheck_method_matches_service_type(self, example_image):
        """Verify health check uses appropriate method for service type."""
        dockerfile_path = f"sdk-examples/{example_image}/Dockerfile"
        with open(dockerfile_path, "r") as f:
            content = f.read()

        # Batch processing services don't need healthchecks - Docker monitors PID 1
        batch_services = ["internal-model", "internal-genai", "internal-agentic"]
        if example_image in batch_services:
            assert (
                "HEALTHCHECK" not in content
            ), f"{example_image} batch service should not have HEALTHCHECK (Docker monitors PID 1)"
            assert (
                "pgrep" not in content
            ), f"{example_image} should not use pgrep (pointless container theater)"

        # API server services use HTTP health endpoints with curl
        api_services = ["vendor-bridge", "registry-genai"]
        if example_image in api_services:
            assert "/health" in content, f"{example_image} should use HTTP health endpoint"
            assert "curl" in content, f"{example_image} should use curl for efficient healthchecks"
            assert "pgrep" not in content, f"{example_image} should not use process check"

    def test_dockerfile_uses_correct_python_version(self, example_image):
        """Verify all Dockerfiles use python:3.11-slim base image."""
        EXPECTED_VERSION = "python:3.11-slim"

        dockerfile_path = f"sdk-examples/{example_image}/Dockerfile"
        with open(dockerfile_path, "r") as f:
            content = f.read()

        # Verify base image
        assert (
            f"FROM {EXPECTED_VERSION}" in content
        ), f"{example_image} Dockerfile should use {EXPECTED_VERSION}"

        # Ensure no other Python versions referenced
        assert (
            "python:3.10" not in content.lower()
        ), f"{example_image} Dockerfile contains incorrect Python 3.10 reference"

    def test_dockerfile_uses_secure_build_pattern(self, example_image):
        """Verify Dockerfile uses secure build pattern (no root-owned files)."""
        dockerfile_path = f"sdk-examples/{example_image}/Dockerfile"
        with open(dockerfile_path, "r") as f:
            content = f.read()

        # Verify uses virtual environment pattern (venv) instead of --user
        assert (
            "python -m venv /opt/venv" in content
        ), f"{example_image} should use virtual environment pattern"

        # Verify USER directive comes before COPY application code in the runtime stage
        lines = content.split("\n")

        # Find the start of the runtime stage (second FROM directive)
        runtime_stage_start = 0
        from_count = 0
        for i, line in enumerate(lines):
            if line.startswith("FROM "):
                from_count += 1
                if from_count == 2:
                    runtime_stage_start = i
                    break

        user_line = None
        copy_app_lines = []

        for i, line in enumerate(lines[runtime_stage_start:], start=runtime_stage_start):
            if "USER appuser" in line:
                user_line = i
            if "COPY" in line and ("mca_sdk" in line or ".py" in line):
                copy_app_lines.append(i)

        assert (
            user_line is not None
        ), f"{example_image} missing 'USER appuser' directive in runtime stage"
        for copy_line in copy_app_lines:
            assert (
                copy_line > user_line
            ), f"{example_image} copies application code before switching to non-root user in runtime stage"

    def test_image_can_build(self, docker_client, example_image):
        """Test that image builds successfully."""
        tag = f"mca-sdk-{example_image}:test"

        try:
            image, build_logs = docker_client.images.build(
                path=".",
                dockerfile=f"sdk-examples/{example_image}/Dockerfile",
                tag=tag,
                rm=True,
            )

            assert image is not None, f"Failed to build {example_image}"
            assert len(image.tags) > 0, f"Image {example_image} has no tags"

        finally:
            try:
                docker_client.images.remove(tag, force=True)
            except:
                pass


class TestDockerImageSecurity:
    """Test Docker image security properties."""

    @pytest.fixture(scope="class")
    def built_image(self, docker_client, example_image):
        """Build image once for security tests."""
        tag = f"mca-sdk-{example_image}:security-test"

        try:
            image, _ = docker_client.images.build(
                path=".",
                dockerfile=f"sdk-examples/{example_image}/Dockerfile",
                tag=tag,
                rm=True,
            )
            yield image
        finally:
            try:
                docker_client.images.remove(tag, force=True)
            except:
                pass

    def test_image_runs_as_non_root(self, docker_client, built_image):
        """Verify container runs as non-root user (UID 1000)."""
        container = docker_client.containers.run(
            built_image.id, command="id -u", detach=False, remove=True
        )

        uid = int(container.decode().strip())
        assert uid == 1000, f"Container running as UID {uid}, expected 1000"

    def test_image_user_is_appuser(self, docker_client, built_image):
        """Verify container runs as 'appuser'."""
        container = docker_client.containers.run(
            built_image.id, command="whoami", detach=False, remove=True
        )

        username = container.decode().strip()
        assert username == "appuser", f"Container running as {username}, expected appuser"

    def test_image_size_is_reasonable(self, built_image):
        """Verify image size is not bloated (should be < 500MB for Python apps)."""
        size_bytes = built_image.attrs["Size"]
        size_mb = size_bytes / (1024 * 1024)

        assert size_mb < 500, f"Image size {size_mb:.2f}MB exceeds 500MB limit"

    def test_image_has_healthcheck(self, built_image):
        """Verify image has healthcheck configuration."""
        config = built_image.attrs.get("Config", {})
        healthcheck = config.get("Healthcheck")

        assert healthcheck is not None, "Image missing HEALTHCHECK configuration"
        assert "Test" in healthcheck, "HEALTHCHECK missing Test directive"


class TestDockerImageFunctionality:
    """Test Docker image runtime functionality."""

    @pytest.fixture(scope="class")
    def otel_collector(self, docker_client):
        """Start OTel Collector for integration testing."""
        container = docker_client.containers.run(
            "otel/opentelemetry-collector:latest",
            detach=True,
            ports={"4318/tcp": 4318, "4317/tcp": 4317},
            name="test-otel-collector",
            remove=True,
        )

        time.sleep(3)  # Wait for collector to start

        yield container

        container.stop()

    def test_container_starts_successfully(self, docker_client, example_image):
        """Verify container starts without errors."""
        tag = f"mca-sdk-{example_image}:start-test"

        try:
            image, _ = docker_client.images.build(
                path=".",
                dockerfile=f"sdk-examples/{example_image}/Dockerfile",
                tag=tag,
                rm=True,
            )

            container = docker_client.containers.run(
                tag,
                detach=True,
                environment={
                    "MCA_OTEL_ENDPOINT": "http://host.docker.internal:4318",
                    "MCA_SERVICE_NAME": f"test-{example_image}",
                    "MCA_MODEL_ID": "test-model-001",
                    "MCA_TEAM_NAME": "test-team",
                },
                remove=True,
            )

            time.sleep(2)  # Wait for startup

            container.reload()
            assert container.status in [
                "running",
                "exited",
            ], f"Container in unexpected state: {container.status}"

            container.stop()

        finally:
            try:
                docker_client.images.remove(tag, force=True)
            except:
                pass

    def test_healthcheck_passes(self, docker_client, example_image):
        """Verify container health check passes."""
        # Skip for examples that exit immediately
        if example_image in ["internal-model", "internal-genai", "internal-agentic"]:
            pytest.skip(f"{example_image} is not a long-running service")

        tag = f"mca-sdk-{example_image}:health-test"

        try:
            image, _ = docker_client.images.build(
                path=".",
                dockerfile=f"sdk-examples/{example_image}/Dockerfile",
                tag=tag,
                rm=True,
            )

            container = docker_client.containers.run(
                tag,
                detach=True,
                environment={
                    "MCA_OTEL_ENDPOINT": "http://host.docker.internal:4318",
                },
                remove=True,
            )

            # Wait for health check (up to 30 seconds)
            for _ in range(30):
                container.reload()
                health = container.attrs.get("State", {}).get("Health", {})
                status = health.get("Status")

                if status == "healthy":
                    break

                time.sleep(1)

            container.stop()

            assert status == "healthy", f"Health check failed: {status}"

        finally:
            try:
                docker_client.images.remove(tag, force=True)
            except:
                pass


class TestDockerImageRegistry:
    """Test pulling images from Artifact Registry."""

    def test_can_pull_from_registry(self, docker_client, image_registry, example_image):
        """Verify images can be pulled from GCP Artifact Registry."""
        if not image_registry:
            pytest.skip("ARTIFACT_REGISTRY_URL not set, skipping registry pull test")

        image_url = f"{image_registry}/{example_image}:latest"

        try:
            image = docker_client.images.pull(image_url)
            assert image is not None, f"Failed to pull {image_url}"

        except docker.errors.ImageNotFound:
            pytest.skip(f"Image not found in registry: {image_url}")

    def test_registry_image_has_labels(self, docker_client, image_registry, example_image):
        """Verify registry images have proper labels."""
        if not image_registry:
            pytest.skip("ARTIFACT_REGISTRY_URL not set")

        image_url = f"{image_registry}/{example_image}:latest"

        try:
            image = docker_client.images.pull(image_url)
            labels = image.attrs.get("Config", {}).get("Labels", {})

            assert labels is not None, "Image missing labels"

        except docker.errors.ImageNotFound:
            pytest.skip(f"Image not found in registry: {image_url}")


@pytest.mark.slow
class TestDockerImagePerformance:
    """Test Docker image build performance."""

    def test_build_time_is_reasonable(self, docker_client, example_image):
        """Verify image builds in reasonable time (< 5 minutes)."""
        tag = f"mca-sdk-{example_image}:perf-test"

        start_time = time.time()

        try:
            image, _ = docker_client.images.build(
                path=".",
                dockerfile=f"sdk-examples/{example_image}/Dockerfile",
                tag=tag,
                rm=True,
            )

            build_time = time.time() - start_time

            assert build_time < 300, f"Build took {build_time:.2f}s, exceeds 5 minute limit"

        finally:
            try:
                docker_client.images.remove(tag, force=True)
            except:
                pass
