"""Unit tests for sshcopyid functionality."""

from __future__ import annotations

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

import pytest

from ..cli import (
    SSHConnectionError,
    SSHCopyIDConfig,
    ssh_copy_id,
)


class TestSSHCopIDConfig:
    """Tests for SSHCopyIDConfig dataclass."""

    def test_config_creation_with_defaults(self):
        """Test configuration creation with default values."""
        config = SSHCopyIDConfig(hostname="localhost", username="testuser", password="testpass")

        assert config.hostname == "localhost"
        assert config.username == "testuser"
        assert config.password == "testpass"
        assert config.port == 22
        assert config.public_key_path == "~/.ssh/id_rsa.pub"
        assert config.timeout == 30
        assert config.connect_timeout == 10

    def test_config_creation_with_custom_values(self):
        """Test configuration creation with custom values."""
        config = SSHCopyIDConfig(
            hostname="192.168.1.100",
            username="admin",
            password="secret",
            port=2222,
            public_key_path="~/.ssh/id_ed25519.pub",
            timeout=60,
            connect_timeout=30,
        )

        assert config.hostname == "192.168.1.100"
        assert config.username == "admin"
        assert config.password == "secret"
        assert config.port == 2222
        assert config.public_key_path == "~/.ssh/id_ed25519.pub"
        assert config.timeout == 60
        assert config.connect_timeout == 30

    def test_expanded_key_path(self):
        """Test expanded key path property."""
        config = SSHCopyIDConfig(
            hostname="localhost",
            username="test",
            password="pass",
            public_key_path="~/.ssh/test_key.pub",
        )

        expected_path = Path.home() / ".ssh/test_key.pub"
        assert config.expanded_key_path == expected_path

    def test_ssh_command_parts(self):
        """Test SSH command parts generation."""
        config = SSHCopyIDConfig(
            hostname="example.com",
            username="user",
            password="pass",
            port=2222,
            connect_timeout=15,
        )

        expected_parts = [
            "ssh",
            "-p",
            "2222",
            "-o",
            "StrictHostKeyChecking=no",
            "-o",
            "UserKnownHostpytolale=/dev/null",
            "-o",
            "ConnectTimeout=15",
            "user@example.com",
        ]

        assert config.ssh_command_parts == expected_parts


class TestSSHCopyIDFunction:
    """Tests for ssh_copy_id function."""

    def test_ssh_copy_id_missing_key_file(self):
        """Test ssh_copy_id with non-existent key file."""
        config = SSHCopyIDConfig(
            hostname="localhost",
            username="test",
            password="pass",
            public_key_path="/non/existent/key.pub",
        )

        with pytest.raises(Exception, match="not found"):
            ssh_copy_id(config)

    def test_ssh_copy_id_empty_key_file(self):
        """Test ssh_copy_id with empty key file."""
        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".pub", delete=False) as tmp_file:
                tmp_file.write("")  # Empty file
                tmp_file.flush()

            config = SSHCopyIDConfig(
                hostname="localhost",
                username="test",
                password="pass",
                public_key_path=tmp_file.name,
            )

            with pytest.raises(Exception, match="empty"):
                ssh_copy_id(config)
        finally:
            if tmp_file:
                with contextlib.suppress(BaseException):
                    Path(tmp_file.name).unlink(missing_ok=True)

    def test_ssh_copy_id_invalid_hostname(self):
        """Test ssh_copy_id with invalid hostname."""
        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".pub", delete=False) as tmp_file:
                tmp_file.write("ssh-rsa AAAAB3NzaC1yc2E test@example.com")
                tmp_file.flush()

            config = SSHCopyIDConfig(
                hostname="",  # Empty hostname
                username="test",
                password="pass",
                public_key_path=tmp_file.name,
            )

            with pytest.raises(Exception, match="cannot be empty"):
                ssh_copy_id(config)
        finally:
            if tmp_file:
                with contextlib.suppress(BaseException):
                    Path(tmp_file.name).unlink(missing_ok=True)

    def test_ssh_copy_id_invalid_port(self):
        """Test ssh_copy_id with invalid port."""
        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".pub", delete=False) as tmp_file:
                tmp_file.write("ssh-rsa AAAAB3NzaC1yc2E test@example.com")
                tmp_file.flush()

            config = SSHCopyIDConfig(
                hostname="localhost",
                username="test",
                password="pass",
                port=99999,  # Invalid port
                public_key_path=tmp_file.name,
            )

            with pytest.raises(Exception, match="Invalid port"):
                ssh_copy_id(config)
        finally:
            if tmp_file:
                with contextlib.suppress(BaseException):
                    Path(tmp_file.name).unlink(missing_ok=True)

    @patch("subprocess.run")
    def test_ssh_copy_id_successful_deployment(self, mock_run):
        """Test successful SSH key deployment."""
        # Mock successful subprocess execution
        mock_process = MagicMock()
        mock_process.returncode = 0
        mock_process.stdout = ""
        mock_process.stderr = ""
        mock_run.return_value = mock_process

        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".pub", delete=False) as tmp_file:
                tmp_file.write("ssh-rsa AAAAB3NzaC1yc2E test@example.com")
                tmp_file.flush()

            config = SSHCopyIDConfig(
                hostname="localhost",
                username="test",
                password="pass",
                public_key_path=tmp_file.name,
            )

            # Should not raise any exceptions
            ssh_copy_id(config)

            # Verify subprocess was called with correct arguments
            mock_run.assert_called_once()
            call_args = mock_run.call_args[0][0]
            assert "sshpass" in call_args
            assert "-p" in call_args
            assert config.username in call_args[-1]  # Last argument should contain username@host
        finally:
            if tmp_file:
                with contextlib.suppress(BaseException):
                    Path(tmp_file.name).unlink(missing_ok=True)

    @patch("subprocess.run")
    def test_ssh_copy_id_authentication_failure(self, mock_run):
        """Test SSH authentication failure handling."""
        # Mock authentication failure
        mock_process = MagicMock()
        mock_process.returncode = 1
        mock_process.stdout = ""
        mock_process.stderr = "Permission denied"
        mock_run.return_value = mock_process

        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".pub", delete=False) as tmp_file:
                tmp_file.write("ssh-rsa AAAAB3NzaC1yc2E test@example.com")
                tmp_file.flush()

            config = SSHCopyIDConfig(
                hostname="localhost",
                username="test",
                password="wrongpass",
                public_key_path=tmp_file.name,
            )

            with pytest.raises(Exception, match="Authentication failed"):
                ssh_copy_id(config)
        finally:
            if tmp_file:
                with contextlib.suppress(BaseException):
                    Path(tmp_file.name).unlink(missing_ok=True)

    @patch("subprocess.run")
    def test_ssh_copy_id_connection_refused(self, mock_run):
        """Test SSH connection refused handling."""
        # Mock connection refused error
        mock_process = MagicMock()
        mock_process.returncode = 1
        mock_process.stdout = ""
        mock_process.stderr = "Connection refused"
        mock_run.return_value = mock_process

        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".pub", delete=False) as tmp_file:
                tmp_file.write("ssh-rsa AAAAB3NzaC1yc2E test@example.com")
                tmp_file.flush()
                tmp_file.close()

            config = SSHCopyIDConfig(
                hostname="nonexistent.host",
                username="test",
                password="pass",
                public_key_path=tmp_file.name,
            )

            with pytest.raises(SSHConnectionError, match="Connection refused"):
                ssh_copy_id(config)
        finally:
            if tmp_file:
                with contextlib.suppress(BaseException):
                    Path(tmp_file.name).unlink(missing_ok=True)

    @patch("subprocess.run")
    def test_ssh_copy_id_timeout(self, mock_run):
        """Test SSH connection timeout handling."""
        # Mock timeout exception
        mock_run.side_effect = TimeoutError("Command timed out")

        tmp_file = None
        try:
            with tempfile.NamedTemporaryFile(mode="w", suffix=".pub", delete=False) as tmp_file:
                tmp_file.write("ssh-rsa AAAAB3NzaC1yc2E test@example.com")
                tmp_file.flush()
                tmp_file.close()

            config = SSHCopyIDConfig(
                hostname="slow.server",
                username="test",
                password="pass",
                public_key_path=tmp_file.name,
                timeout=5,
            )

            with pytest.raises(Exception, match="timed out"):
                ssh_copy_id(config)
        finally:
            if tmp_file:
                with contextlib.suppress(BaseException):
                    Path(tmp_file.name).unlink(missing_ok=True)


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