"""Tests for the remote host management module."""

from __future__ import annotations

import tempfile
from datetime import datetime
from pathlib import Path
from unittest.mock import MagicMock, patch

import pytest

from gaol.remote.models import (
    HostConfig,
    HostConnectionStatus,
    HostsRegistry,
    HostState,
    RemoteCommandResult,
    StoredHost,
)
from gaol.remote.exceptions import (
    ConfigSyncError,
    HostExistsError,
    HostNotFoundError,
    KeyNotFoundError,
    GaolNotFoundError,
    RemoteCommandError,
    RemoteError,
    SSHAuthenticationError,
    SSHConnectionError,
    SSHTimeoutError,
)


# =============================================================================
# Model Tests
# =============================================================================


class TestHostConnectionStatus:
    """Tests for HostConnectionStatus enum."""

    def test_status_values(self) -> None:
        """Test all status values exist."""
        assert HostConnectionStatus.UNKNOWN.value == "unknown"
        assert HostConnectionStatus.REACHABLE.value == "reachable"
        assert HostConnectionStatus.UNREACHABLE.value == "unreachable"
        assert HostConnectionStatus.AUTH_FAILED.value == "auth_failed"
        assert HostConnectionStatus.TIMEOUT.value == "timeout"
        assert HostConnectionStatus.ERROR.value == "error"


class TestHostConfig:
    """Tests for HostConfig model."""

    def test_default_values(self) -> None:
        """Test default configuration values."""
        config = HostConfig(name="test", hostname="example.com")
        assert config.user == "root"
        assert config.port == 22
        assert config.key_path is None
        assert config.key_secret_name is None
        assert config.connect_timeout == 10
        assert config.strict_host_key_checking is False
        assert config.gaol_path == "gaol"
        assert config.description is None
        assert config.tags == []

    def test_full_config(self) -> None:
        """Test fully specified configuration."""
        config = HostConfig(
            name="production",
            hostname="prod.example.com",
            user="deploy",
            port=2222,
            key_path="~/.ssh/prod_key",
            connect_timeout=30,
            strict_host_key_checking=True,
            gaol_path="/usr/local/bin/gaol",
            description="Production server",
            tags=["prod", "critical"],
        )
        assert config.name == "production"
        assert config.hostname == "prod.example.com"
        assert config.user == "deploy"
        assert config.port == 2222
        assert config.key_path == "~/.ssh/prod_key"
        assert config.connect_timeout == 30
        assert config.strict_host_key_checking is True
        assert config.gaol_path == "/usr/local/bin/gaol"
        assert config.description == "Production server"
        assert config.tags == ["prod", "critical"]

    def test_ssh_destination(self) -> None:
        """Test ssh_destination property."""
        config = HostConfig(name="test", hostname="example.com", user="admin")
        assert config.ssh_destination == "admin@example.com"

    def test_name_validation(self) -> None:
        """Test host name validation."""
        # Valid names
        HostConfig(name="test", hostname="example.com")
        HostConfig(name="my-host", hostname="example.com")
        HostConfig(name="host_123", hostname="example.com")
        HostConfig(name="Host.name", hostname="example.com")

        # Invalid names
        with pytest.raises(ValueError):
            HostConfig(name="", hostname="example.com")  # Empty
        with pytest.raises(ValueError):
            HostConfig(name="test host", hostname="example.com")  # Space
        with pytest.raises(ValueError):
            HostConfig(name="-test", hostname="example.com")  # Starts with dash

    def test_hostname_validation(self) -> None:
        """Test hostname cannot be empty."""
        with pytest.raises(ValueError):
            HostConfig(name="test", hostname="")
        with pytest.raises(ValueError):
            HostConfig(name="test", hostname="   ")

    def test_port_validation(self) -> None:
        """Test port number validation."""
        # Valid ports
        HostConfig(name="test", hostname="example.com", port=1)
        HostConfig(name="test", hostname="example.com", port=22)
        HostConfig(name="test", hostname="example.com", port=65535)

        # Invalid ports
        with pytest.raises(ValueError):
            HostConfig(name="test", hostname="example.com", port=0)
        with pytest.raises(ValueError):
            HostConfig(name="test", hostname="example.com", port=65536)

    def test_timeout_validation(self) -> None:
        """Test connection timeout validation."""
        # Valid timeouts
        HostConfig(name="test", hostname="example.com", connect_timeout=1)
        HostConfig(name="test", hostname="example.com", connect_timeout=300)

        # Invalid timeouts
        with pytest.raises(ValueError):
            HostConfig(name="test", hostname="example.com", connect_timeout=0)
        with pytest.raises(ValueError):
            HostConfig(name="test", hostname="example.com", connect_timeout=301)

    def test_frozen(self) -> None:
        """Test HostConfig is frozen (immutable)."""
        config = HostConfig(name="test", hostname="example.com")
        with pytest.raises(Exception):  # Pydantic raises ValidationError for frozen models
            config.name = "new_name"


class TestHostState:
    """Tests for HostState model."""

    def test_default_values(self) -> None:
        """Test default state values."""
        state = HostState()
        assert state.status == HostConnectionStatus.UNKNOWN
        assert state.last_check is None
        assert state.last_successful_connection is None
        assert state.error_message is None
        assert state.gaol_version is None
        assert state.os_info is None

    def test_reachable_state(self) -> None:
        """Test reachable state with all fields."""
        now = datetime.now()
        state = HostState(
            status=HostConnectionStatus.REACHABLE,
            last_check=now,
            last_successful_connection=now,
            gaol_version="0.1.0",
            os_info="Linux 5.15",
        )
        assert state.status == HostConnectionStatus.REACHABLE
        assert state.last_check == now
        assert state.gaol_version == "0.1.0"

    def test_error_state(self) -> None:
        """Test error state with message."""
        state = HostState(
            status=HostConnectionStatus.AUTH_FAILED,
            error_message="Permission denied",
        )
        assert state.status == HostConnectionStatus.AUTH_FAILED
        assert state.error_message == "Permission denied"


class TestStoredHost:
    """Tests for StoredHost model."""

    def test_create_stored_host(self) -> None:
        """Test creating a stored host."""
        config = HostConfig(name="test", hostname="example.com")
        now = datetime.now()

        host = StoredHost(
            config=config,
            created_at=now,
            updated_at=now,
        )
        assert host.config.name == "test"
        assert host.state.status == HostConnectionStatus.UNKNOWN
        assert host.created_at == now
        assert host.updated_at == now

    def test_update_state(self) -> None:
        """Test update_state method."""
        config = HostConfig(name="test", hostname="example.com")
        host = StoredHost(
            config=config,
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )

        # Update to reachable
        host.update_state(HostConnectionStatus.REACHABLE, gaol_version="1.0.0")
        assert host.state.status == HostConnectionStatus.REACHABLE
        assert host.state.last_check is not None
        assert host.state.last_successful_connection is not None
        assert host.state.gaol_version == "1.0.0"
        assert host.state.error_message is None

        # Update to unreachable
        host.update_state(HostConnectionStatus.UNREACHABLE, error_message="Connection refused")
        assert host.state.status == HostConnectionStatus.UNREACHABLE
        assert host.state.error_message == "Connection refused"


class TestRemoteCommandResult:
    """Tests for RemoteCommandResult model."""

    def test_successful_result(self) -> None:
        """Test successful command result."""
        result = RemoteCommandResult(
            host_name="test",
            exit_code=0,
            stdout="output",
            stderr="",
            duration_ms=100,
        )
        assert result.success is True
        assert result.exit_code == 0
        assert result.stdout == "output"
        assert result.duration_ms == 100

    def test_failed_result(self) -> None:
        """Test failed command result."""
        result = RemoteCommandResult(
            host_name="test",
            exit_code=1,
            stdout="",
            stderr="error",
            duration_ms=50,
        )
        assert result.success is False
        assert result.exit_code == 1
        assert result.stderr == "error"

    def test_with_parsed_json(self) -> None:
        """Test result with parsed JSON."""
        result = RemoteCommandResult(
            host_name="test",
            exit_code=0,
            stdout='{"key": "value"}',
            stderr="",
            duration_ms=100,
            parsed_json={"key": "value"},
        )
        assert result.parsed_json == {"key": "value"}

    def test_frozen(self) -> None:
        """Test RemoteCommandResult is frozen."""
        result = RemoteCommandResult(
            host_name="test",
            exit_code=0,
            stdout="",
            stderr="",
            duration_ms=100,
        )
        with pytest.raises(Exception):
            result.exit_code = 1


class TestHostsRegistry:
    """Tests for HostsRegistry model."""

    def test_empty_registry(self) -> None:
        """Test empty registry."""
        registry = HostsRegistry()
        assert registry.version == 1
        assert registry.hosts == {}
        assert registry.list_hosts() == []

    def test_add_host(self) -> None:
        """Test adding a host."""
        registry = HostsRegistry()
        config = HostConfig(name="test", hostname="example.com")
        host = StoredHost(
            config=config,
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )

        registry.add_host(host)
        assert "test" in registry.hosts
        assert registry.get_host("test") == host

    def test_remove_host(self) -> None:
        """Test removing a host."""
        registry = HostsRegistry()
        config = HostConfig(name="test", hostname="example.com")
        host = StoredHost(
            config=config,
            created_at=datetime.now(),
            updated_at=datetime.now(),
        )

        registry.add_host(host)
        assert registry.remove_host("test") is True
        assert registry.get_host("test") is None
        assert registry.remove_host("test") is False  # Already removed

    def test_list_hosts(self) -> None:
        """Test listing hosts."""
        registry = HostsRegistry()

        for i in range(3):
            config = HostConfig(name=f"host{i}", hostname=f"host{i}.example.com")
            host = StoredHost(
                config=config,
                created_at=datetime.now(),
                updated_at=datetime.now(),
            )
            registry.add_host(host)

        hosts = registry.list_hosts()
        assert len(hosts) == 3
        assert all(isinstance(h, StoredHost) for h in hosts)


# =============================================================================
# Exception Tests
# =============================================================================


class TestRemoteExceptions:
    """Tests for remote exception hierarchy."""

    def test_base_remote_error(self) -> None:
        """Test base RemoteError."""
        error = RemoteError("Test error")
        assert str(error) == "Test error"
        assert error.message == "Test error"

    def test_host_not_found_error(self) -> None:
        """Test HostNotFoundError."""
        error = HostNotFoundError("myhost")
        assert error.host_name == "myhost"
        assert "myhost" in str(error)

    def test_host_exists_error(self) -> None:
        """Test HostExistsError."""
        error = HostExistsError("myhost")
        assert error.host_name == "myhost"
        assert "already exists" in str(error)

    def test_ssh_connection_error(self) -> None:
        """Test SSHConnectionError."""
        error = SSHConnectionError("example.com", "Connection refused")
        assert error.host == "example.com"
        assert error.reason == "Connection refused"
        assert "example.com" in str(error)
        assert "Connection refused" in str(error)

    def test_ssh_connection_error_no_reason(self) -> None:
        """Test SSHConnectionError without reason."""
        error = SSHConnectionError("example.com")
        assert error.reason is None
        assert "example.com" in str(error)

    def test_ssh_authentication_error(self) -> None:
        """Test SSHAuthenticationError."""
        error = SSHAuthenticationError("example.com", "admin")
        assert error.host == "example.com"
        assert error.user == "admin"
        assert "admin@example.com" in str(error)

    def test_ssh_timeout_error(self) -> None:
        """Test SSHTimeoutError."""
        error = SSHTimeoutError("example.com", 10)
        assert error.host == "example.com"
        assert error.timeout == 10
        assert "10s" in str(error)

    def test_remote_command_error(self) -> None:
        """Test RemoteCommandError."""
        error = RemoteCommandError("myhost", "ls -la", 1, "No such file")
        assert error.host == "myhost"
        assert error.command == "ls -la"
        assert error.exit_code == 1
        assert error.stderr == "No such file"

    def test_gaol_not_found_error(self) -> None:
        """Test GaolNotFoundError."""
        error = GaolNotFoundError("myhost", "/usr/bin/gaol")
        assert error.host == "myhost"
        assert error.path == "/usr/bin/gaol"

    def test_config_sync_error(self) -> None:
        """Test ConfigSyncError."""
        error = ConfigSyncError("sync_to", "File not found")
        assert error.operation == "sync_to"
        assert error.reason == "File not found"

    def test_key_not_found_error(self) -> None:
        """Test KeyNotFoundError."""
        error = KeyNotFoundError("~/.ssh/missing_key")
        assert error.path == "~/.ssh/missing_key"


# =============================================================================
# Store Tests
# =============================================================================


class TestHostStore:
    """Tests for HostStore persistence."""

    def test_save_and_get_host(self) -> None:
        """Test saving and retrieving a host."""
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))

            config = HostConfig(name="test", hostname="example.com")
            host = StoredHost(
                config=config,
                created_at=datetime.now(),
                updated_at=datetime.now(),
            )

            store.save_host(host)

            retrieved = store.get_host("test")
            assert retrieved is not None
            assert retrieved.config.name == "test"
            assert retrieved.config.hostname == "example.com"

    def test_host_exists(self) -> None:
        """Test checking if host exists."""
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))

            assert store.host_exists("test") is False

            config = HostConfig(name="test", hostname="example.com")
            host = StoredHost(
                config=config,
                created_at=datetime.now(),
                updated_at=datetime.now(),
            )
            store.save_host(host)

            assert store.host_exists("test") is True

    def test_delete_host(self) -> None:
        """Test deleting a host."""
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))

            config = HostConfig(name="test", hostname="example.com")
            host = StoredHost(
                config=config,
                created_at=datetime.now(),
                updated_at=datetime.now(),
            )
            store.save_host(host)

            assert store.delete_host("test") is True
            assert store.host_exists("test") is False
            assert store.delete_host("test") is False

    def test_list_hosts(self) -> None:
        """Test listing all hosts."""
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))

            for i in range(3):
                config = HostConfig(name=f"host{i}", hostname=f"host{i}.example.com")
                host = StoredHost(
                    config=config,
                    created_at=datetime.now(),
                    updated_at=datetime.now(),
                )
                store.save_host(host)

            hosts = store.list_hosts()
            assert len(hosts) == 3

    def test_file_permissions(self) -> None:
        """Test that store files have restrictive permissions."""
        import os
        import stat

        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))

            config = HostConfig(name="test", hostname="example.com")
            host = StoredHost(
                config=config,
                created_at=datetime.now(),
                updated_at=datetime.now(),
            )
            store.save_host(host)

            # Check directory permissions (700)
            dir_stat = os.stat(tmpdir)
            assert dir_stat.st_mode & 0o777 == stat.S_IRWXU

            # Check file permissions (600)
            file_path = Path(tmpdir) / "hosts.yaml"
            file_stat = os.stat(file_path)
            assert file_stat.st_mode & 0o777 == stat.S_IRUSR | stat.S_IWUSR


# =============================================================================
# Executor Tests
# =============================================================================


class TestSSHExecutor:
    """Tests for SSHExecutor."""

    def test_build_ssh_command_basic(self) -> None:
        """Test building basic SSH command."""
        from gaol.remote.executor import SSHExecutor

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com", user="admin")

        cmd = executor.build_ssh_command(config)

        assert "ssh" in cmd
        assert "-o" in cmd
        assert "ConnectTimeout=10" in cmd
        assert "admin@example.com" in cmd
        assert "-p" in cmd
        assert "22" in cmd

    def test_build_ssh_command_with_key(self) -> None:
        """Test building SSH command with key file."""
        from gaol.remote.executor import SSHExecutor

        with tempfile.NamedTemporaryFile(delete=False) as f:
            key_path = f.name

        try:
            executor = SSHExecutor()
            config = HostConfig(name="test", hostname="example.com", key_path=key_path)

            cmd = executor.build_ssh_command(config)

            assert "-i" in cmd
            assert key_path in cmd
        finally:
            Path(key_path).unlink()

    def test_build_ssh_command_missing_key(self) -> None:
        """Test building SSH command with missing key file."""
        from gaol.remote.executor import SSHExecutor

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com", key_path="/nonexistent/key")

        with pytest.raises(KeyNotFoundError):
            executor.build_ssh_command(config)

    def test_build_ssh_command_strict_host_checking(self) -> None:
        """Test building SSH command with strict host key checking."""
        from gaol.remote.executor import SSHExecutor

        executor = SSHExecutor()
        config = HostConfig(
            name="test",
            hostname="example.com",
            strict_host_key_checking=True,
        )

        cmd = executor.build_ssh_command(config)

        # Should NOT have StrictHostKeyChecking=no
        assert "StrictHostKeyChecking=no" not in " ".join(cmd)

    def test_build_ssh_command_custom_port(self) -> None:
        """Test building SSH command with custom port."""
        from gaol.remote.executor import SSHExecutor

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com", port=2222)

        cmd = executor.build_ssh_command(config)

        assert "-p" in cmd
        idx = cmd.index("-p")
        assert cmd[idx + 1] == "2222"

    def test_build_scp_command(self) -> None:
        """Test building SCP command."""
        from gaol.remote.executor import SSHExecutor

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com", user="admin")

        cmd = executor.build_scp_command(config)

        assert "scp" in cmd
        assert "-P" in cmd  # Note: SCP uses uppercase P for port

    def test_build_scp_command_recursive(self) -> None:
        """Test building recursive SCP command."""
        from gaol.remote.executor import SSHExecutor

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        cmd = executor.build_scp_command(config, recursive=True)

        assert "-r" in cmd

    @patch("subprocess.run")
    def test_execute_success(self, mock_run: MagicMock) -> None:
        """Test successful command execution."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="output",
            stderr="",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        result = executor.execute(config, ["ls", "-la"])

        assert result.success is True
        assert result.stdout == "output"
        assert result.exit_code == 0

    @patch("subprocess.run")
    def test_execute_auth_failure(self, mock_run: MagicMock) -> None:
        """Test command execution with auth failure."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=255,
            stdout="",
            stderr="Permission denied (publickey)",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        with pytest.raises(SSHAuthenticationError):
            executor.execute(config, ["ls"])

    @patch("subprocess.run")
    def test_execute_connection_refused(self, mock_run: MagicMock) -> None:
        """Test command execution with connection refused."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=255,
            stdout="",
            stderr="Connection refused",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        with pytest.raises(SSHConnectionError) as exc_info:
            executor.execute(config, ["ls"])
        assert "Connection refused" in str(exc_info.value)

    @patch("subprocess.run")
    def test_execute_with_check(self, mock_run: MagicMock) -> None:
        """Test command execution with check=True."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=1,
            stdout="",
            stderr="error",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        with pytest.raises(RemoteCommandError):
            executor.execute(config, ["ls"], check=True)

    @patch("subprocess.run")
    def test_exec_gaol_json(self, mock_run: MagicMock) -> None:
        """Test gaol command with JSON output."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=0,
            stdout='[{"name": "container1"}]',
            stderr="",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        result = executor.exec_gaol(config, ["list"])

        assert result.parsed_json == [{"name": "container1"}]

    @patch("subprocess.run")
    def test_exec_gaol_not_found(self, mock_run: MagicMock) -> None:
        """Test gaol command when binary not found."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=127,
            stdout="",
            stderr="gaol: command not found",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        with pytest.raises(GaolNotFoundError):
            executor.exec_gaol(config, ["list"])

    @patch("subprocess.run")
    def test_check_connection_success(self, mock_run: MagicMock) -> None:
        """Test successful connection check."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="ok\n",
            stderr="",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        reachable, error = executor.check_connection(config)

        assert reachable is True
        assert error is None

    @patch("subprocess.run")
    def test_check_connection_failure(self, mock_run: MagicMock) -> None:
        """Test failed connection check."""
        from gaol.remote.executor import SSHExecutor

        mock_run.return_value = MagicMock(
            returncode=255,
            stdout="",
            stderr="Connection refused",
        )

        executor = SSHExecutor()
        config = HostConfig(name="test", hostname="example.com")

        reachable, error = executor.check_connection(config)

        assert reachable is False
        assert error is not None


# =============================================================================
# Manager Tests
# =============================================================================


class TestRemoteManager:
    """Tests for RemoteManager."""

    def test_add_host_no_verify(self) -> None:
        """Test adding a host without verification."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            config = HostConfig(name="test", hostname="example.com")
            host = manager.add_host(config, verify=False)

            assert host.config.name == "test"
            assert host.state.status == HostConnectionStatus.UNKNOWN

    def test_add_host_duplicate(self) -> None:
        """Test adding a duplicate host."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            config = HostConfig(name="test", hostname="example.com")
            manager.add_host(config, verify=False)

            with pytest.raises(HostExistsError):
                manager.add_host(config, verify=False)

    def test_remove_host(self) -> None:
        """Test removing a host."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            config = HostConfig(name="test", hostname="example.com")
            manager.add_host(config, verify=False)

            assert manager.remove_host("test") is True
            assert manager.remove_host("test") is False

    def test_get_host(self) -> None:
        """Test getting a host."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            config = HostConfig(name="test", hostname="example.com")
            manager.add_host(config, verify=False)

            host = manager.get_host("test")
            assert host.config.name == "test"

    def test_get_host_not_found(self) -> None:
        """Test getting a nonexistent host."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            with pytest.raises(HostNotFoundError):
                manager.get_host("nonexistent")

    def test_list_hosts(self) -> None:
        """Test listing hosts."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            for i in range(3):
                config = HostConfig(name=f"host{i}", hostname=f"host{i}.example.com")
                manager.add_host(config, verify=False)

            hosts = manager.list_hosts()
            assert len(hosts) == 3

    @patch("subprocess.run")
    def test_check_host_reachable(self, mock_run: MagicMock) -> None:
        """Test checking a reachable host."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        # First call for check_connection, second for check_gaol
        mock_run.side_effect = [
            MagicMock(returncode=0, stdout="ok\n", stderr=""),
            MagicMock(returncode=0, stdout="gaol 0.1.0", stderr=""),
        ]

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            config = HostConfig(name="test", hostname="example.com")
            manager.add_host(config, verify=False)

            state = manager.check_host("test")
            assert state.status == HostConnectionStatus.REACHABLE

    @patch("subprocess.run")
    def test_execute(self, mock_run: MagicMock) -> None:
        """Test executing a command."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="output",
            stderr="",
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            config = HostConfig(name="test", hostname="example.com")
            manager.add_host(config, verify=False)

            result = manager.execute("test", ["ls"])
            assert result.success is True
            assert result.stdout == "output"

    @patch("subprocess.run")
    def test_list_containers(self, mock_run: MagicMock) -> None:
        """Test listing containers on remote."""
        from gaol.remote.manager import RemoteManager
        from gaol.remote.store import HostStore

        mock_run.return_value = MagicMock(
            returncode=0,
            stdout='[{"name": "container1"}, {"name": "container2"}]',
            stderr="",
        )

        with tempfile.TemporaryDirectory() as tmpdir:
            store = HostStore(store_dir=Path(tmpdir))
            manager = RemoteManager(store=store)

            config = HostConfig(name="test", hostname="example.com")
            manager.add_host(config, verify=False)

            containers = manager.list_containers("test")
            assert len(containers) == 2
            assert containers[0]["name"] == "container1"


# =============================================================================
# Integration Tests
# =============================================================================


class TestModuleExports:
    """Tests for module exports."""

    def test_imports(self) -> None:
        """Test that all expected symbols are exported."""
        from gaol.remote import (
            ConfigSyncError,
            HostConfig,
            HostConnectionStatus,
            HostExistsError,
            HostNotFoundError,
            HostsRegistry,
            HostState,
            HostStore,
            KeyNotFoundError,
            GaolNotFoundError,
            RemoteCommandError,
            RemoteCommandResult,
            RemoteError,
            RemoteManager,
            SSHAuthenticationError,
            SSHConnectionError,
            SSHExecutor,
            SSHTimeoutError,
            StoredHost,
            get_remote_manager,
        )

        # Just verify imports work
        assert HostConfig is not None
        assert RemoteManager is not None
        assert get_remote_manager is not None
