"""Unit tests for readiness probe service."""

from unittest.mock import MagicMock, patch


from src.services.shared.readiness_probe import execute_probe


class TestExecuteProbe:
    """Tests for execute_probe function."""

    @patch("subprocess.run")
    async def test_probe_succeeds_first_attempt(self, mock_run) -> None:
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="healthy",
            stderr="",
        )
        
        result, logs = await execute_probe("container-123", "curl -f http://localhost:8080")
        
        assert result is True
        assert any("Success" in log for log in logs)
        mock_run.assert_called_once_with(
            ["docker", "exec", "container-123", "sh", "-c", "curl -f http://localhost:8080"],
            capture_output=True,
            text=True,
            timeout=2,
        )

    @patch("subprocess.run")
    async def test_probe_fails_then_succeeds(self, mock_run) -> None:
        mock_run.side_effect = [
            MagicMock(returncode=1, stdout="", stderr="Connection refused"),
            MagicMock(returncode=1, stdout="", stderr="Connection refused"),
            MagicMock(returncode=0, stdout="healthy", stderr=""),
        ]
        
        result, logs = await execute_probe("container-123", "curl -f http://localhost:8080", timeout=10, interval=0.1)
        
        assert result is True
        assert mock_run.call_count == 3
        assert any("Attempt 1: Failed" in log for log in logs)
        assert any("Attempt 3: Success" in log for log in logs)

    @patch("subprocess.run")
    async def test_probe_times_out(self, mock_run) -> None:
        mock_run.return_value = MagicMock(
            returncode=1,
            stdout="",
            stderr="Connection refused",
        )
        
        result, logs = await execute_probe("container-123", "curl -f http://localhost:8080", timeout=0.5, interval=0.2)
        
        assert result is False
        assert any("timed out" in log.lower() for log in logs)

    @patch("subprocess.run")
    async def test_probe_command_not_found(self, mock_run) -> None:
        mock_run.return_value = MagicMock(
            returncode=127,
            stdout="",
            stderr="command not found",
        )
        
        result, logs = await execute_probe("container-123", "nonexistent-command", timeout=1, interval=0.3)
        
        assert result is False
        assert any("exit code 127" in log for log in logs)

    @patch("subprocess.run")
    async def test_probe_exception(self, mock_run) -> None:
        mock_run.side_effect = OSError("Docker not available")
        
        result, logs = await execute_probe("container-123", "curl http://localhost", timeout=1, interval=0.3)
        
        assert result is False
        assert any("Error" in log for log in logs)

    @patch("subprocess.run")
    async def test_probe_with_special_characters(self, mock_run) -> None:
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="",
            stderr="",
        )
        
        cmd = "bash -c 'echo \"hello world\" && exit 0'"
        await execute_probe("container-123", cmd)
        
        call_args = mock_run.call_args
        assert cmd in call_args[0][0]

    @patch("subprocess.run")
    async def test_probe_captures_stdout(self, mock_run) -> None:
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="Server is ready\\nVersion: 1.0",
            stderr="",
        )
        
        result, logs = await execute_probe("container-123", "cat /app/status")
        
        assert result is True
        assert any("Server is ready" in log for log in logs)


class TestIntegrationScenarios:
    """Integration-style tests with realistic scenarios."""

    @patch("subprocess.run")
    async def test_web_server_probe(self, mock_run) -> None:
        """Test typical web server health check."""
        mock_run.side_effect = [
            MagicMock(returncode=1, stdout="", stderr=""),
            MagicMock(returncode=1, stdout="", stderr=""),
            MagicMock(returncode=1, stdout="", stderr=""),
            MagicMock(returncode=0, stdout="OK", stderr=""),
        ]
        
        result, logs = await execute_probe(
            "web-container",
            "curl -f http://localhost:8080/health",
            timeout=10,
            interval=0.2,
        )
        
        assert result is True
        assert mock_run.call_count == 4

    @patch("subprocess.run")
    async def test_command_probe(self, mock_run) -> None:
        """Test command availability check."""
        mock_run.return_value = MagicMock(
            returncode=0,
            stdout="opencode 1.0.0",
            stderr="",
        )
        
        result, logs = await execute_probe(
            "tool-container",
            "which opencode && opencode --version",
            timeout=30,
            interval=2,
        )
        
        assert result is True
        assert any("opencode 1.0.0" in log for log in logs)

    @patch("subprocess.run")
    async def test_database_probe(self, mock_run) -> None:
        """Test database readiness check."""
        mock_run.side_effect = [
            MagicMock(returncode=1, stdout="", stderr=""),
            MagicMock(returncode=1, stdout="", stderr=""),
            MagicMock(returncode=0, stdout="/var/run/postgresql:5432 - accepting connections", stderr=""),
        ]
        
        result, logs = await execute_probe(
            "db-container",
            "pg_isready -U postgres",
            timeout=10,
            interval=0.3,
        )
        
        assert result is True
        assert mock_run.call_count == 3

    @patch("subprocess.run")
    async def test_file_probe(self, mock_run) -> None:
        """Test file existence check."""
        mock_run.return_value = MagicMock(returncode=0, stdout="", stderr="")
        
        result, logs = await execute_probe(
            "app-container",
            "[ -f /app/ready ]",
            timeout=10,
            interval=1,
        )
        
        assert result is True

    @patch("subprocess.run")
    async def test_slow_starting_service(self, mock_run) -> None:
        """Test service that takes time to start."""
        # Simulate 5 failures before success
        side_effects = [MagicMock(returncode=1, stdout="", stderr="")] * 5
        side_effects.append(MagicMock(returncode=0, stdout="Ready", stderr=""))
        mock_run.side_effect = side_effects
        
        result, logs = await execute_probe(
            "slow-container",
            "curl -f http://localhost:8080",
            timeout=10,
            interval=0.2,
        )
        
        assert result is True
        assert mock_run.call_count == 6
        assert any("Attempt 6: Success" in log for log in logs)

    @patch("subprocess.run")
    async def test_zero_timeout_immediate_return(self, mock_run) -> None:
        mock_run.return_value = MagicMock(returncode=1, stdout="", stderr="")
        
        result, logs = await execute_probe(
            "container",
            "test",
            timeout=0,
            interval=1,
        )
        
        assert result is False
        assert any("timed out" in log.lower() for log in logs)
