"""
Tests for laneswap.health module.

This module tests health check registry and response building functionality.
"""

from typing import Any, Dict
from unittest.mock import Mock, patch

import pytest

from laneswap.health import (
    AsyncHealthCheckFunc,
    HealthCheckFunc,
    HealthCheckRegistry,
    build_health_response,
)


class TestHealthCheckRegistry:
    """Test HealthCheckRegistry class."""

    def test_init_creates_empty_registry(self) -> None:
        """Test that registry initializes with no checks."""
        registry = HealthCheckRegistry()
        assert registry._checks == []
        assert registry._async_checks == []

    def test_register_sync_function(self) -> None:
        """Test registering a synchronous health check function."""
        registry = HealthCheckRegistry()

        def sync_check() -> Dict[str, str]:
            return {"status": "ok"}

        registry.register(sync_check)
        assert len(registry._checks) == 1
        assert len(registry._async_checks) == 0
        assert registry._checks[0] == sync_check

    def test_register_async_function(self) -> None:
        """Test registering an asynchronous health check function."""
        registry = HealthCheckRegistry()

        async def async_check() -> Dict[str, str]:
            return {"status": "ok"}

        registry.register(async_check)
        assert len(registry._async_checks) == 1
        assert len(registry._checks) == 0
        assert registry._async_checks[0] == async_check

    def test_register_multiple_sync_functions(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test registering multiple synchronous functions."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])
        registry.register(sample_health_checks["sync_cache"])

        assert len(registry._checks) == 2
        assert len(registry._async_checks) == 0

    def test_register_multiple_async_functions(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test registering multiple asynchronous functions."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["async_redis"])
        registry.register(sample_health_checks["async_api"])

        assert len(registry._async_checks) == 2
        assert len(registry._checks) == 0

    def test_register_mixed_sync_and_async(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test registering both sync and async functions."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])
        registry.register(sample_health_checks["async_redis"])
        registry.register(sample_health_checks["sync_cache"])
        registry.register(sample_health_checks["async_api"])

        assert len(registry._checks) == 2
        assert len(registry._async_checks) == 2

    def test_run_checks_with_no_checks(self) -> None:
        """Test run_checks returns empty dict when no checks registered."""
        registry = HealthCheckRegistry()
        result = registry.run_checks()
        assert result == {}

    def test_run_checks_with_single_check(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test run_checks with a single health check."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])

        result = registry.run_checks()
        assert result == {"database": "connected"}

    def test_run_checks_with_multiple_checks(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test run_checks merges results from multiple checks."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])
        registry.register(sample_health_checks["sync_cache"])

        result = registry.run_checks()
        assert result == {"database": "connected", "cache": "ready"}

    def test_run_checks_with_none_result(self) -> None:
        """Test run_checks handles health check that returns None."""
        registry = HealthCheckRegistry()

        def check_returns_none() -> None:
            return None

        registry.register(check_returns_none)
        result = registry.run_checks()
        assert result == {}

    def test_run_checks_with_mixed_results(self) -> None:
        """Test run_checks handles mix of dict and None returns."""
        registry = HealthCheckRegistry()

        def check1() -> Dict[str, str]:
            return {"check1": "ok"}

        def check2() -> None:
            return None

        def check3() -> Dict[str, str]:
            return {"check3": "ok"}

        registry.register(check1)
        registry.register(check2)
        registry.register(check3)

        result = registry.run_checks()
        assert result == {"check1": "ok", "check3": "ok"}

    def test_run_checks_handles_exception(self) -> None:
        """Test run_checks handles exceptions in health checks gracefully."""
        registry = HealthCheckRegistry()

        def failing_check() -> Dict[str, str]:
            raise RuntimeError("Check failed")

        registry.register(failing_check)
        result = registry.run_checks()

        assert "error" in result
        assert "Check failed" in result["error"]

    def test_run_checks_continues_after_exception(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test run_checks continues executing after one check fails."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])
        registry.register(sample_health_checks["failing"])
        registry.register(sample_health_checks["sync_cache"])

        result = registry.run_checks()

        # Should have results from successful checks
        assert "database" in result
        assert "cache" in result
        # And error from failed check
        assert "error" in result

    def test_run_checks_ignores_non_dict_returns(self) -> None:
        """Test run_checks ignores health checks that don't return dicts."""
        registry = HealthCheckRegistry()

        def returns_string() -> str:
            return "healthy"

        def returns_list() -> list:
            return ["ok"]

        def returns_dict() -> Dict[str, str]:
            return {"status": "ok"}

        registry.register(returns_string)  # type: ignore
        registry.register(returns_list)  # type: ignore
        registry.register(returns_dict)

        result = registry.run_checks()
        assert result == {"status": "ok"}

    @pytest.mark.asyncio
    async def test_run_async_checks_with_no_checks(self) -> None:
        """Test run_async_checks returns empty dict when no async checks."""
        registry = HealthCheckRegistry()
        result = await registry.run_async_checks()
        assert result == {}

    @pytest.mark.asyncio
    async def test_run_async_checks_with_single_check(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test run_async_checks with a single async check."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["async_redis"])

        result = await registry.run_async_checks()
        assert result == {"redis": "connected"}

    @pytest.mark.asyncio
    async def test_run_async_checks_with_multiple_checks(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test run_async_checks merges results from multiple async checks."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["async_redis"])
        registry.register(sample_health_checks["async_api"])

        result = await registry.run_async_checks()
        assert result == {"redis": "connected", "api": "responsive"}

    @pytest.mark.asyncio
    async def test_run_async_checks_handles_none_result(self) -> None:
        """Test run_async_checks handles async check that returns None."""
        registry = HealthCheckRegistry()

        async def check_returns_none() -> None:
            return None

        registry.register(check_returns_none)
        result = await registry.run_async_checks()
        assert result == {}

    @pytest.mark.asyncio
    async def test_run_async_checks_handles_exception(self) -> None:
        """Test run_async_checks handles exceptions gracefully."""
        registry = HealthCheckRegistry()

        async def failing_check() -> Dict[str, str]:
            raise RuntimeError("Async check failed")

        registry.register(failing_check)
        result = await registry.run_async_checks()

        assert "error" in result
        assert "Async check failed" in result["error"]

    @pytest.mark.asyncio
    async def test_run_async_checks_continues_after_exception(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test run_async_checks continues after one check fails."""

        registry = HealthCheckRegistry()

        async def failing_async() -> Dict[str, str]:
            raise RuntimeError("Failed")

        registry.register(sample_health_checks["async_redis"])
        registry.register(failing_async)
        registry.register(sample_health_checks["async_api"])

        result = await registry.run_async_checks()

        # Should have results from successful checks
        assert "redis" in result
        assert "api" in result
        # And error from failed check
        assert "error" in result

    def test_has_async_checks_returns_false_initially(self) -> None:
        """Test has_async_checks returns False for new registry."""
        registry = HealthCheckRegistry()
        assert registry.has_async_checks() is False

    def test_has_async_checks_returns_true_when_async_registered(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test has_async_checks returns True when async check registered."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["async_redis"])
        assert registry.has_async_checks() is True

    def test_has_async_checks_returns_false_when_only_sync(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test has_async_checks returns False when only sync checks."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])
        assert registry.has_async_checks() is False

    def test_has_async_checks_with_mixed_checks(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test has_async_checks returns True with mixed sync/async checks."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])
        registry.register(sample_health_checks["async_redis"])
        assert registry.has_async_checks() is True

    def test_clear_removes_all_checks(
        self, sample_health_checks: Dict[str, Any]
    ) -> None:
        """Test clear() removes all registered checks."""
        registry = HealthCheckRegistry()
        registry.register(sample_health_checks["sync_database"])
        registry.register(sample_health_checks["async_redis"])

        assert len(registry._checks) == 1
        assert len(registry._async_checks) == 1

        registry.clear()

        assert len(registry._checks) == 0
        assert len(registry._async_checks) == 0
        assert registry.has_async_checks() is False

    def test_clear_on_empty_registry(self) -> None:
        """Test clear() works on empty registry."""
        registry = HealthCheckRegistry()
        registry.clear()
        assert len(registry._checks) == 0
        assert len(registry._async_checks) == 0


class TestBuildHealthResponse:
    """Test build_health_response function."""

    def test_builds_basic_response(self, mock_time: Any) -> None:
        """Test building basic health response with minimal args."""
        response = build_health_response("test-service")

        assert response["status"] == "healthy"
        assert response["service"] == "test-service"
        assert response["timestamp"] == 1234567890.123
        assert len(response) == 3

    def test_builds_response_with_custom_status(self, mock_time: Any) -> None:
        """Test building health response with custom status."""
        response = build_health_response("test-service", status="degraded")

        assert response["status"] == "degraded"
        assert response["service"] == "test-service"

    def test_builds_response_with_custom_checks(self, mock_time: Any) -> None:
        """Test building health response with custom check results."""
        custom_checks = {"database": "connected", "cache": "ready"}
        response = build_health_response("test-service", custom_checks=custom_checks)

        assert response["status"] == "healthy"
        assert response["service"] == "test-service"
        assert response["database"] == "connected"
        assert response["cache"] == "ready"
        assert len(response) == 5

    def test_builds_response_with_empty_custom_checks(self, mock_time: Any) -> None:
        """Test building health response with empty custom checks dict."""
        response = build_health_response("test-service", custom_checks={})

        assert response["status"] == "healthy"
        assert response["service"] == "test-service"
        assert len(response) == 3

    def test_builds_response_with_none_custom_checks(self, mock_time: Any) -> None:
        """Test building health response with None custom checks."""
        response = build_health_response("test-service", custom_checks=None)

        assert response["status"] == "healthy"
        assert response["service"] == "test-service"
        assert len(response) == 3

    def test_custom_checks_merge_correctly(self, mock_time: Any) -> None:
        """Test that custom checks are merged into response."""
        custom_checks = {
            "component1": "ok",
            "component2": "ok",
            "uptime": 12345,
        }
        response = build_health_response("test-service", custom_checks=custom_checks)

        assert "component1" in response
        assert "component2" in response
        assert "uptime" in response
        assert response["component1"] == "ok"
        assert response["uptime"] == 12345

    def test_timestamp_is_float(self) -> None:
        """Test that timestamp is a float (Unix timestamp)."""
        response = build_health_response("test-service")
        assert isinstance(response["timestamp"], float)

    def test_timestamp_is_current_time(self) -> None:
        """Test that timestamp reflects current time."""
        import time

        before = time.time()
        response = build_health_response("test-service")
        after = time.time()

        assert before <= response["timestamp"] <= after

    def test_all_status_values_work(self, mock_time: Any) -> None:
        """Test that all common status values work."""
        statuses = ["healthy", "unhealthy", "degraded", "unknown"]

        for status in statuses:
            response = build_health_response("test-service", status=status)
            assert response["status"] == status

    def test_custom_checks_can_override_keys(self, mock_time: Any) -> None:
        """Test that custom checks can include any keys."""
        custom_checks = {
            "custom_field": "value",
            "nested": {"key": "value"},
            "number": 42,
        }
        response = build_health_response("test-service", custom_checks=custom_checks)

        assert response["custom_field"] == "value"
        assert response["nested"] == {"key": "value"}
        assert response["number"] == 42

    def test_response_always_includes_required_fields(self, mock_time: Any) -> None:
        """Test that response always has status, service, timestamp."""
        response = build_health_response("test-service", custom_checks={"extra": "field"})

        assert "status" in response
        assert "service" in response
        assert "timestamp" in response

    def test_service_name_can_be_any_string(self, mock_time: Any) -> None:
        """Test that service name accepts any string value."""
        test_names = [
            "simple",
            "with-dashes",
            "with_underscores",
            "with.dots",
            "with/slashes",
            "special!@#$%",
            "",
        ]

        for name in test_names:
            response = build_health_response(name)
            assert response["service"] == name
