"""Tests for ssh_keys module."""

import os
from pathlib import Path
from unittest.mock import MagicMock, patch

from compose_farm.config import Host
from compose_farm.executor import ssh_connect_kwargs
from compose_farm.ssh_keys import (
    SSH_KEY_PATH,
    get_key_path,
    get_pubkey_content,
    get_ssh_auth_sock,
    get_ssh_env,
    key_exists,
)


class TestGetSshAuthSock:
    """Tests for get_ssh_auth_sock function."""

    def test_returns_env_var_when_socket_exists(self) -> None:
        """Return SSH_AUTH_SOCK env var if the socket exists."""
        mock_path = MagicMock()
        mock_path.is_socket.return_value = True

        with (
            patch.dict(os.environ, {"SSH_AUTH_SOCK": "/tmp/agent.sock"}),
            patch("compose_farm.ssh_keys.Path", return_value=mock_path),
        ):
            result = get_ssh_auth_sock()
            assert result == "/tmp/agent.sock"

    def test_returns_none_when_env_var_not_socket(self, tmp_path: Path) -> None:
        """Return None if SSH_AUTH_SOCK points to non-socket."""
        regular_file = tmp_path / "not_a_socket"
        regular_file.touch()
        with (
            patch.dict(os.environ, {"SSH_AUTH_SOCK": str(regular_file)}),
            patch("compose_farm.ssh_keys.Path.home", return_value=tmp_path),
        ):
            # Should fall through to agent dir check, which won't exist
            result = get_ssh_auth_sock()
            assert result is None

    def test_finds_agent_in_ssh_agent_dir(self, tmp_path: Path) -> None:
        """Find agent socket in ~/.ssh/agent/ directory."""
        # Create agent directory structure with a regular file
        agent_dir = tmp_path / ".ssh" / "agent"
        agent_dir.mkdir(parents=True)
        sock_path = agent_dir / "s.12345.sshd.67890"
        sock_path.touch()  # Create as regular file

        with (
            patch.dict(os.environ, {}, clear=False),
            patch("compose_farm.ssh_keys.Path.home", return_value=tmp_path),
            patch.object(Path, "is_socket", return_value=True),
        ):
            os.environ.pop("SSH_AUTH_SOCK", None)
            result = get_ssh_auth_sock()
            assert result == str(sock_path)

    def test_returns_none_when_no_agent_found(self, tmp_path: Path) -> None:
        """Return None when no SSH agent socket is found."""
        with (
            patch.dict(os.environ, {}, clear=False),
            patch("compose_farm.ssh_keys.Path.home", return_value=tmp_path),
        ):
            os.environ.pop("SSH_AUTH_SOCK", None)
            result = get_ssh_auth_sock()
            assert result is None


class TestGetSshEnv:
    """Tests for get_ssh_env function."""

    def test_returns_env_with_ssh_auth_sock(self) -> None:
        """Return env dict with SSH_AUTH_SOCK set."""
        with patch("compose_farm.ssh_keys.get_ssh_auth_sock", return_value="/tmp/agent.sock"):
            result = get_ssh_env()
            assert result["SSH_AUTH_SOCK"] == "/tmp/agent.sock"
            # Should include other env vars too
            assert "PATH" in result or len(result) > 1

    def test_returns_env_without_ssh_auth_sock_when_none(self, tmp_path: Path) -> None:
        """Return env without SSH_AUTH_SOCK when no agent found."""
        with (
            patch.dict(os.environ, {}, clear=False),
            patch("compose_farm.ssh_keys.Path.home", return_value=tmp_path),
        ):
            os.environ.pop("SSH_AUTH_SOCK", None)
            result = get_ssh_env()
            # SSH_AUTH_SOCK should not be set if no agent found
            assert result.get("SSH_AUTH_SOCK") is None


class TestKeyExists:
    """Tests for key_exists function."""

    def test_returns_true_when_both_keys_exist(self, tmp_path: Path) -> None:
        """Return True when both private and public keys exist."""
        key_path = tmp_path / "compose-farm"
        pubkey_path = tmp_path / "compose-farm.pub"
        key_path.touch()
        pubkey_path.touch()

        with (
            patch("compose_farm.ssh_keys.SSH_KEY_PATH", key_path),
            patch("compose_farm.ssh_keys.SSH_PUBKEY_PATH", pubkey_path),
        ):
            assert key_exists() is True

    def test_returns_false_when_private_key_missing(self, tmp_path: Path) -> None:
        """Return False when private key doesn't exist."""
        key_path = tmp_path / "compose-farm"
        pubkey_path = tmp_path / "compose-farm.pub"
        pubkey_path.touch()  # Only public key exists

        with (
            patch("compose_farm.ssh_keys.SSH_KEY_PATH", key_path),
            patch("compose_farm.ssh_keys.SSH_PUBKEY_PATH", pubkey_path),
        ):
            assert key_exists() is False

    def test_returns_false_when_public_key_missing(self, tmp_path: Path) -> None:
        """Return False when public key doesn't exist."""
        key_path = tmp_path / "compose-farm"
        pubkey_path = tmp_path / "compose-farm.pub"
        key_path.touch()  # Only private key exists

        with (
            patch("compose_farm.ssh_keys.SSH_KEY_PATH", key_path),
            patch("compose_farm.ssh_keys.SSH_PUBKEY_PATH", pubkey_path),
        ):
            assert key_exists() is False


class TestGetKeyPath:
    """Tests for get_key_path function."""

    def test_returns_path_when_key_exists(self) -> None:
        """Return key path when key exists."""
        with patch("compose_farm.ssh_keys.key_exists", return_value=True):
            result = get_key_path()
            assert result == SSH_KEY_PATH

    def test_returns_none_when_key_missing(self) -> None:
        """Return None when key doesn't exist."""
        with patch("compose_farm.ssh_keys.key_exists", return_value=False):
            result = get_key_path()
            assert result is None


class TestGetPubkeyContent:
    """Tests for get_pubkey_content function."""

    def test_returns_content_when_exists(self, tmp_path: Path) -> None:
        """Return public key content when file exists."""
        pubkey_content = "ssh-ed25519 AAAA... compose-farm"
        pubkey_path = tmp_path / "compose-farm.pub"
        pubkey_path.write_text(pubkey_content + "\n")

        with patch("compose_farm.ssh_keys.SSH_PUBKEY_PATH", pubkey_path):
            result = get_pubkey_content()
            assert result == pubkey_content

    def test_returns_none_when_missing(self, tmp_path: Path) -> None:
        """Return None when public key doesn't exist."""
        pubkey_path = tmp_path / "compose-farm.pub"  # Doesn't exist

        with patch("compose_farm.ssh_keys.SSH_PUBKEY_PATH", pubkey_path):
            result = get_pubkey_content()
            assert result is None


class TestSshConnectKwargs:
    """Tests for ssh_connect_kwargs function."""

    def test_basic_kwargs(self) -> None:
        """Return basic connection kwargs."""
        host = Host(address="example.com", port=22, user="testuser")

        with (
            patch("compose_farm.executor.get_ssh_auth_sock", return_value=None),
            patch("compose_farm.executor.get_key_path", return_value=None),
        ):
            result = ssh_connect_kwargs(host)

            assert result["host"] == "example.com"
            assert result["port"] == 22
            assert result["username"] == "testuser"
            assert result["known_hosts"] is None
            assert "agent_path" not in result
            assert "client_keys" not in result

    def test_includes_agent_path_when_available(self) -> None:
        """Include agent_path when SSH agent is available."""
        host = Host(address="example.com")

        with (
            patch("compose_farm.executor.get_ssh_auth_sock", return_value="/tmp/agent.sock"),
            patch("compose_farm.executor.get_key_path", return_value=None),
        ):
            result = ssh_connect_kwargs(host)

            assert result["agent_path"] == "/tmp/agent.sock"

    def test_includes_client_keys_when_key_exists(self, tmp_path: Path) -> None:
        """Include client_keys when compose-farm key exists."""
        host = Host(address="example.com")
        key_path = tmp_path / "compose-farm"

        with (
            patch("compose_farm.executor.get_ssh_auth_sock", return_value=None),
            patch("compose_farm.executor.get_key_path", return_value=key_path),
        ):
            result = ssh_connect_kwargs(host)

            assert result["client_keys"] == [str(key_path)]

    def test_includes_both_agent_and_key(self, tmp_path: Path) -> None:
        """Include both agent_path and client_keys when both available."""
        host = Host(address="example.com")
        key_path = tmp_path / "compose-farm"

        with (
            patch("compose_farm.executor.get_ssh_auth_sock", return_value="/tmp/agent.sock"),
            patch("compose_farm.executor.get_key_path", return_value=key_path),
        ):
            result = ssh_connect_kwargs(host)

            assert result["agent_path"] == "/tmp/agent.sock"
            assert result["client_keys"] == [str(key_path)]

    def test_custom_port(self) -> None:
        """Handle custom SSH port."""
        host = Host(address="example.com", port=2222)

        with (
            patch("compose_farm.executor.get_ssh_auth_sock", return_value=None),
            patch("compose_farm.executor.get_key_path", return_value=None),
        ):
            result = ssh_connect_kwargs(host)

            assert result["port"] == 2222
