"""
Unit tests for the isolation module.
"""

import pytest
import shutil
from pathlib import Path
from unittest.mock import patch, MagicMock

from agent_orchestrator.workspace.isolation import (
    IsolationManager,
    IsolationConfig,
    IsolationType,
    IsolatedEnvironment,
    ResourceLimits,
    CredentialConfig,
    NetworkMode,
)


class TestResourceLimits:
    """Tests for ResourceLimits."""

    def test_default_limits(self):
        """Test default resource limits."""
        limits = ResourceLimits()
        assert limits.memory_mb == 4096
        assert limits.cpu_cores == 2.0
        assert limits.disk_gb == 10
        assert limits.pids_limit == 100

    def test_custom_limits(self):
        """Test custom resource limits."""
        limits = ResourceLimits(
            memory_mb=8192,
            cpu_cores=4.0,
            disk_gb=20,
            pids_limit=200,
        )
        assert limits.memory_mb == 8192
        assert limits.cpu_cores == 4.0

    def test_to_docker_args(self):
        """Test converting limits to Docker args."""
        limits = ResourceLimits(memory_mb=2048, cpu_cores=1.5, pids_limit=50)
        args = limits.to_docker_args()

        assert "--memory=2048m" in args
        assert "--cpus=1.5" in args
        assert "--pids-limit=50" in args


class TestCredentialConfig:
    """Tests for CredentialConfig."""

    def test_default_config(self):
        """Test default credential configuration."""
        creds = CredentialConfig()
        assert creds.inject_git_config is True
        assert creds.inject_ssh_keys is False
        assert creds.inject_aws_credentials is False

    def test_get_env_dict_with_custom(self):
        """Test getting env dict with custom vars."""
        creds = CredentialConfig(
            custom_env={"API_KEY": "secret", "DEBUG": "true"},
        )
        env = creds.get_env_dict()
        assert env["API_KEY"] == "secret"
        assert env["DEBUG"] == "true"

    def test_get_env_dict_with_system_vars(self):
        """Test getting env vars from system."""
        with patch.dict("os.environ", {"TEST_VAR": "test_value"}):
            creds = CredentialConfig(inject_env_vars=["TEST_VAR"])
            env = creds.get_env_dict()
            assert env["TEST_VAR"] == "test_value"

    def test_get_env_dict_missing_var(self):
        """Test handling missing env var."""
        creds = CredentialConfig(inject_env_vars=["NONEXISTENT_VAR"])
        env = creds.get_env_dict()
        assert "NONEXISTENT_VAR" not in env


class TestIsolationConfig:
    """Tests for IsolationConfig."""

    def test_default_config(self):
        """Test default isolation configuration."""
        config = IsolationConfig()
        assert config.isolation_type == IsolationType.TMUX
        assert config.container_image == "ubuntu:22.04"
        assert config.network_mode == NetworkMode.BRIDGE
        assert config.privileged is False

    def test_docker_config(self):
        """Test Docker isolation configuration."""
        config = IsolationConfig(
            isolation_type=IsolationType.DOCKER,
            container_image="node:18",
            network_mode=NetworkMode.HOST,
            resource_limits=ResourceLimits(memory_mb=8192),
        )
        assert config.isolation_type == IsolationType.DOCKER
        assert config.container_image == "node:18"
        assert config.resource_limits.memory_mb == 8192

    def test_to_dict(self):
        """Test converting config to dictionary."""
        config = IsolationConfig(
            isolation_type=IsolationType.PODMAN,
            network_mode=NetworkMode.NONE,
        )
        data = config.to_dict()

        assert data["isolation_type"] == "podman"
        assert data["network_mode"] == "none"
        assert "resource_limits" in data
        assert "credentials" in data


class TestIsolatedEnvironment:
    """Tests for IsolatedEnvironment."""

    def test_tmux_environment(self):
        """Test tmux-based environment."""
        env = IsolatedEnvironment(
            agent_id="agent-1",
            isolation_type=IsolationType.TMUX,
            workspace_path=Path("/workspace"),
            tmux_session="agent-agent-1",
        )
        assert env.is_container is False
        assert env.tmux_session == "agent-agent-1"

    def test_docker_environment(self):
        """Test Docker-based environment."""
        env = IsolatedEnvironment(
            agent_id="agent-2",
            isolation_type=IsolationType.DOCKER,
            workspace_path=Path("/workspace"),
            container_id="abc123",
        )
        assert env.is_container is True
        assert env.runtime_cmd == "docker"

    def test_podman_environment(self):
        """Test Podman-based environment."""
        env = IsolatedEnvironment(
            agent_id="agent-3",
            isolation_type=IsolationType.PODMAN,
            workspace_path=Path("/workspace"),
            container_id="def456",
        )
        assert env.is_container is True
        assert env.runtime_cmd == "podman"


class TestIsolationManager:
    """Tests for IsolationManager."""

    @pytest.fixture
    def manager(self) -> IsolationManager:
        """Create an isolation manager."""
        return IsolationManager()

    def test_get_available_runtimes(self, manager: IsolationManager):
        """Test getting available runtimes."""
        runtimes = manager.get_available_runtimes()

        # tmux should typically be available in test environments
        # Docker/Podman may or may not be
        assert isinstance(runtimes, list)
        for rt in runtimes:
            assert isinstance(rt, IsolationType)

    @pytest.mark.skipif(
        not shutil.which("tmux"),
        reason="tmux not available"
    )
    def test_create_tmux_environment(self, manager: IsolationManager, tmp_path: Path):
        """Test creating a tmux environment."""
        env = manager.create_environment(
            agent_id="test-tmux",
            workspace_path=tmp_path,
            config=IsolationConfig(isolation_type=IsolationType.TMUX),
        )

        try:
            assert env.isolation_type == IsolationType.TMUX
            assert env.agent_id == "test-tmux"
            assert manager.is_running("test-tmux") is True
        finally:
            manager.cleanup("test-tmux")

    @pytest.mark.skipif(
        not shutil.which("tmux"),
        reason="tmux not available"
    )
    def test_execute_tmux_command(self, manager: IsolationManager, tmp_path: Path):
        """Test executing command in tmux."""
        manager.create_environment(
            agent_id="test-cmd",
            workspace_path=tmp_path,
            config=IsolationConfig(isolation_type=IsolationType.TMUX),
        )

        try:
            # Execute a simple command
            manager.execute_command("test-cmd", "echo hello", interactive=True)

            # Capture output
            output = manager.capture_output("test-cmd", lines=10)
            assert isinstance(output, str)
        finally:
            manager.cleanup("test-cmd")

    def test_create_environment_missing_runtime(self, manager: IsolationManager, tmp_path: Path):
        """Test creating environment with unavailable runtime."""
        # Mock no runtimes available
        manager._tmux_available = False
        manager._docker_available = False
        manager._podman_available = False

        with pytest.raises(RuntimeError, match="not available"):
            manager.create_environment(
                agent_id="test-fail",
                workspace_path=tmp_path,
            )

    def test_list_environments(self, manager: IsolationManager):
        """Test listing environments."""
        # Initially empty
        assert manager.list_environments() == []

    def test_get_environment(self, manager: IsolationManager):
        """Test getting specific environment."""
        # Non-existent
        assert manager.get_environment("nonexistent") is None

    def test_cleanup_nonexistent(self, manager: IsolationManager):
        """Test cleanup of non-existent environment."""
        # Should not raise
        manager.cleanup("nonexistent")

    def test_stop_nonexistent(self, manager: IsolationManager):
        """Test stopping non-existent environment."""
        # Should not raise
        manager.stop("nonexistent")


class TestIsolationManagerDocker:
    """Tests for Docker/Podman container isolation."""

    @pytest.fixture
    def docker_available(self) -> bool:
        """Check if Docker is available."""
        return shutil.which("docker") is not None

    @pytest.fixture
    def manager(self) -> IsolationManager:
        """Create an isolation manager."""
        return IsolationManager()

    @pytest.mark.skipif(
        not shutil.which("docker"),
        reason="Docker not available"
    )
    def test_create_docker_environment(self, manager: IsolationManager, tmp_path: Path):
        """Test creating a Docker environment."""
        config = IsolationConfig(
            isolation_type=IsolationType.DOCKER,
            container_image="alpine:latest",
            resource_limits=ResourceLimits(memory_mb=256, cpu_cores=0.5),
        )

        env = manager.create_environment(
            agent_id="test-docker",
            workspace_path=tmp_path,
            config=config,
        )

        try:
            assert env.isolation_type == IsolationType.DOCKER
            assert env.container_id is not None
            assert manager.is_running("test-docker") is True
        finally:
            manager.cleanup("test-docker")

    @pytest.mark.skipif(
        not shutil.which("docker"),
        reason="Docker not available"
    )
    def test_execute_docker_command(self, manager: IsolationManager, tmp_path: Path):
        """Test executing command in Docker container."""
        config = IsolationConfig(
            isolation_type=IsolationType.DOCKER,
            container_image="alpine:latest",
        )

        manager.create_environment(
            agent_id="test-docker-cmd",
            workspace_path=tmp_path,
            config=config,
        )

        try:
            output = manager.execute_command(
                "test-docker-cmd",
                "echo hello",
                interactive=False,
            )
            assert "hello" in output
        finally:
            manager.cleanup("test-docker-cmd")

    @pytest.mark.skipif(
        not shutil.which("docker"),
        reason="Docker not available"
    )
    def test_docker_resource_limits(self, manager: IsolationManager, tmp_path: Path):
        """Test Docker resource limits are applied."""
        config = IsolationConfig(
            isolation_type=IsolationType.DOCKER,
            container_image="alpine:latest",
            resource_limits=ResourceLimits(memory_mb=128),
        )

        manager.create_environment(
            agent_id="test-limits",
            workspace_path=tmp_path,
            config=config,
        )

        try:
            assert manager.is_running("test-limits") is True
        finally:
            manager.cleanup("test-limits")

    @pytest.mark.skipif(
        not shutil.which("docker"),
        reason="Docker not available"
    )
    def test_docker_network_mode(self, manager: IsolationManager, tmp_path: Path):
        """Test Docker network mode configuration."""
        config = IsolationConfig(
            isolation_type=IsolationType.DOCKER,
            container_image="alpine:latest",
            network_mode=NetworkMode.NONE,
        )

        manager.create_environment(
            agent_id="test-network",
            workspace_path=tmp_path,
            config=config,
        )

        try:
            # Container should be running without network
            assert manager.is_running("test-network") is True
        finally:
            manager.cleanup("test-network")


class TestIsolationIntegration:
    """Integration tests for isolation with workspace manager."""

    @pytest.mark.skipif(
        not shutil.which("tmux"),
        reason="tmux not available"
    )
    def test_workspace_with_isolation(self, tmp_path: Path):
        """Test combining workspace manager with isolation."""
        manager = IsolationManager()

        # Create workspace directory
        workspace = tmp_path / "agent-workspace"
        workspace.mkdir()

        # Create isolated environment
        env = manager.create_environment(
            agent_id="combined-test",
            workspace_path=workspace,
            config=IsolationConfig(
                isolation_type=IsolationType.TMUX,
                credentials=CredentialConfig(
                    inject_git_config=True,
                    custom_env={"PROJECT": "test"},
                ),
            ),
        )

        try:
            assert env.workspace_path == workspace
            assert manager.is_running("combined-test") is True

            # Check environment info
            info = manager.get_environment("combined-test")
            assert info is not None
            assert info.config is not None
        finally:
            manager.cleanup("combined-test")

    def test_cleanup_all(self, tmp_path: Path):
        """Test cleaning up all environments."""
        manager = IsolationManager()

        # Skip if no runtimes available
        if not manager.get_available_runtimes():
            pytest.skip("No isolation runtimes available")

        # Create multiple environments
        for i in range(3):
            workspace = tmp_path / f"workspace-{i}"
            workspace.mkdir()
            manager.create_environment(
                agent_id=f"multi-{i}",
                workspace_path=workspace,
            )

        assert len(manager.list_environments()) == 3

        manager.cleanup_all()

        assert len(manager.list_environments()) == 0
