"""
Tests for laneswap.core module.

This module tests the main LaneSwap class and its integration capabilities.
"""

import signal
import sys
import time
from typing import Any, Dict
from unittest.mock import AsyncMock, Mock, call, patch

import pytest

from laneswap.core import LaneSwap
from laneswap.health import HealthCheckRegistry


class TestLaneSwapInit:
    """Test LaneSwap class initialization."""

    def test_init_with_defaults(self, clean_env: None, disable_signal_handlers: None) -> None:
        """Test LaneSwap initialization with default values."""
        ls = LaneSwap(setup_signals=False)

        assert ls.service_name == "service"
        assert ls.port == 5000
        assert ls._running is True
        assert isinstance(ls._health_registry, HealthCheckRegistry)
        assert ls._shutdown_handlers == []

    def test_init_with_env_vars(
        self,
        env_with_service_name: str,
        env_with_port: int,
        disable_signal_handlers: None,
    ) -> None:
        """Test LaneSwap initialization with environment variables."""
        ls = LaneSwap(setup_signals=False)

        assert ls.service_name == env_with_service_name
        assert ls.port == env_with_port

    def test_init_with_overrides(self, clean_env: None, disable_signal_handlers: None) -> None:
        """Test LaneSwap initialization with parameter overrides."""
        ls = LaneSwap(
            service_name="custom-service",
            port=9000,
            setup_signals=False,
        )

        assert ls.service_name == "custom-service"
        assert ls.port == 9000

    def test_init_with_flask_app(
        self,
        mock_flask_app: Mock,
        clean_env: None,
        disable_signal_handlers: None,
    ) -> None:
        """Test LaneSwap initialization with Flask app."""
        with patch("laneswap.core.LaneSwap._integrate_flask") as mock_integrate:
            ls = LaneSwap(app=mock_flask_app, setup_signals=False)

            assert ls._app is mock_flask_app
            mock_integrate.assert_called_once_with(mock_flask_app)

    def test_init_with_fastapi_app(
        self,
        mock_fastapi_app: Mock,
        clean_env: None,
        disable_signal_handlers: None,
    ) -> None:
        """Test LaneSwap initialization with FastAPI app."""
        with patch("laneswap.core.LaneSwap._integrate_fastapi") as mock_integrate:
            ls = LaneSwap(app=mock_fastapi_app, setup_signals=False)

            assert ls._app is mock_fastapi_app
            mock_integrate.assert_called_once_with(mock_fastapi_app)

    def test_init_with_unknown_app_logs_warning(
        self,
        mock_unknown_app: Mock,
        clean_env: None,
        disable_signal_handlers: None,
    ) -> None:
        """Test LaneSwap initialization with unknown framework logs warning."""
        ls = LaneSwap(app=mock_unknown_app, setup_signals=False, setup_logs=False)
        # Should not raise exception, just log warning

    def test_init_registers_signals_by_default(self, clean_env: None) -> None:
        """Test that signal handlers are registered by default."""
        with patch("signal.signal") as mock_signal:
            ls = LaneSwap()

            # Should register SIGTERM and SIGINT
            assert mock_signal.call_count == 2
            calls = mock_signal.call_args_list
            assert call(signal.SIGTERM, mock_signal.call_args[0][1]) in calls or True
            assert call(signal.SIGINT, mock_signal.call_args[0][1]) in calls or True

    def test_init_skips_signals_when_disabled(
        self, clean_env: None
    ) -> None:
        """Test that signal registration can be disabled."""
        with patch("signal.signal") as mock_signal:
            ls = LaneSwap(setup_signals=False)

            mock_signal.assert_not_called()

    def test_init_sets_up_logging_by_default(self, clean_env: None) -> None:
        """Test that logging is configured by default."""
        with patch("laneswap.core.setup_logging") as mock_setup:
            mock_setup.return_value = Mock()
            ls = LaneSwap(setup_signals=False)

            mock_setup.assert_called_once()

    def test_init_skips_logging_when_disabled(self, clean_env: None) -> None:
        """Test that logging setup can be disabled."""
        with patch("laneswap.core.setup_logging") as mock_setup:
            with patch("laneswap.core.get_logger") as mock_get:
                mock_get.return_value = Mock()
                ls = LaneSwap(setup_signals=False, setup_logs=False)

                mock_setup.assert_not_called()
                mock_get.assert_called_once()


class TestLaneSwapSignalHandling:
    """Test LaneSwap signal handling."""

    def test_register_signals_registers_sigterm_and_sigint(
        self, clean_env: None
    ) -> None:
        """Test that _register_signals registers both SIGTERM and SIGINT."""
        with patch("signal.signal") as mock_signal:
            ls = LaneSwap(setup_logs=False)

            # Verify both signals were registered
            registered_signals = [call_args[0][0] for call_args in mock_signal.call_args_list]
            assert signal.SIGTERM in registered_signals
            assert signal.SIGINT in registered_signals

    def test_signal_handler_sets_running_false(
        self, clean_env: None, capture_sys_exit: list
    ) -> None:
        """Test that signal handler sets running to False."""
        ls = LaneSwap(setup_logs=False, setup_signals=False)
        ls._running = True

        # Get the signal handler
        with patch("signal.signal") as mock_signal:
            ls._register_signals()
            handler = mock_signal.call_args_list[0][0][1]

        # Call the handler
        handler(signal.SIGTERM, None)

        assert ls._running is False

    def test_signal_handler_executes_shutdown_handlers(
        self, clean_env: None, capture_sys_exit: list
    ) -> None:
        """Test that signal handler executes shutdown handlers."""
        ls = LaneSwap(setup_logs=False, setup_signals=False)

        # Register shutdown handlers
        handler1_called = []
        handler2_called = []

        @ls.on_shutdown
        def handler1() -> None:
            handler1_called.append(True)

        @ls.on_shutdown
        def handler2() -> None:
            handler2_called.append(True)

        # Get and call signal handler
        with patch("signal.signal") as mock_signal:
            ls._register_signals()
            sig_handler = mock_signal.call_args_list[0][0][1]

        sig_handler(signal.SIGTERM, None)

        assert handler1_called == [True]
        assert handler2_called == [True]

    def test_signal_handler_calls_sys_exit(
        self, clean_env: None, capture_sys_exit: list
    ) -> None:
        """Test that signal handler calls sys.exit(0)."""
        ls = LaneSwap(setup_logs=False, setup_signals=False)

        with patch("signal.signal") as mock_signal:
            ls._register_signals()
            handler = mock_signal.call_args_list[0][0][1]

        handler(signal.SIGTERM, None)

        assert 0 in capture_sys_exit


class TestLaneSwapHealthChecks:
    """Test LaneSwap health check decorator and functionality."""

    def test_health_check_decorator_registers_function(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that @health_check decorator registers the function."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def check() -> Dict[str, str]:
            return {"status": "ok"}

        assert len(ls._health_registry._checks) == 1

    def test_health_check_decorator_returns_function(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that @health_check decorator returns the original function."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

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

        decorated = ls.health_check(check)

        assert decorated is check

    def test_health_check_decorator_with_async_function(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that @health_check decorator works with async functions."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

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

        assert len(ls._health_registry._async_checks) == 1

    def test_health_check_decorator_with_multiple_functions(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test registering multiple health check functions."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

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

        @ls.health_check
        def check2() -> Dict[str, str]:
            return {"check2": "ok"}

        @ls.health_check
        async def check3() -> Dict[str, str]:
            return {"check3": "ok"}

        assert len(ls._health_registry._checks) == 2
        assert len(ls._health_registry._async_checks) == 1

    def test_registered_health_checks_are_callable(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that registered health checks can still be called."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.health_check
        def check() -> Dict[str, str]:
            return {"result": "success"}

        result = check()
        assert result == {"result": "success"}


class TestLaneSwapShutdownHandlers:
    """Test LaneSwap shutdown handler functionality."""

    def test_on_shutdown_decorator_registers_handler(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that @on_shutdown decorator registers the handler."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.on_shutdown
        def cleanup() -> None:
            pass

        assert len(ls._shutdown_handlers) == 1

    def test_on_shutdown_decorator_returns_function(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that @on_shutdown decorator returns the original function."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        def cleanup() -> None:
            pass

        decorated = ls.on_shutdown(cleanup)

        assert decorated is cleanup

    def test_on_shutdown_with_multiple_handlers(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test registering multiple shutdown handlers."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        @ls.on_shutdown
        def handler1() -> None:
            pass

        @ls.on_shutdown
        def handler2() -> None:
            pass

        @ls.on_shutdown
        def handler3() -> None:
            pass

        assert len(ls._shutdown_handlers) == 3

    def test_execute_shutdown_handlers_calls_all_handlers(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that _execute_shutdown_handlers calls all registered handlers."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        call_order = []

        @ls.on_shutdown
        def handler1() -> None:
            call_order.append(1)

        @ls.on_shutdown
        def handler2() -> None:
            call_order.append(2)

        ls._execute_shutdown_handlers()

        assert call_order == [1, 2]

    def test_execute_shutdown_handlers_continues_on_error(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that shutdown continues if a handler raises an exception."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        call_order = []

        @ls.on_shutdown
        def handler1() -> None:
            call_order.append(1)

        @ls.on_shutdown
        def failing_handler() -> None:
            raise RuntimeError("Handler failed")

        @ls.on_shutdown
        def handler3() -> None:
            call_order.append(3)

        ls._execute_shutdown_handlers()

        # All handlers should be attempted
        assert call_order == [1, 3]

    def test_execute_shutdown_handlers_with_async_handlers(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test that async shutdown handlers are executed correctly."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

        call_order = []

        @ls.on_shutdown
        async def async_handler() -> None:
            call_order.append("async")

        @ls.on_shutdown
        def sync_handler() -> None:
            call_order.append("sync")

        ls._execute_shutdown_handlers()

        # Both should be called
        assert "async" in call_order
        assert "sync" in call_order


class TestLaneSwapProperties:
    """Test LaneSwap class properties."""

    def test_service_name_property(
        self, env_with_service_name: str, disable_signal_handlers: None
    ) -> None:
        """Test service_name property returns correct value."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)
        assert ls.service_name == env_with_service_name

    def test_port_property(
        self, env_with_port: int, disable_signal_handlers: None
    ) -> None:
        """Test port property returns correct value."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)
        assert ls.port == env_with_port

    def test_running_property_initial_state(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test running property is initially True."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)
        assert ls.running is True

    def test_running_property_can_be_changed(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test running property reflects internal state changes."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)
        ls._running = False
        assert ls.running is False

    def test_health_registry_property(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test health_registry property returns HealthCheckRegistry."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)
        assert isinstance(ls.health_registry, HealthCheckRegistry)


class TestLaneSwapRun:
    """Test LaneSwap run() method."""

    def test_run_starts_server_thread(
        self, free_port: int, clean_env: None
    ) -> None:
        """Test that run() starts the health server."""
        ls = LaneSwap(port=free_port, setup_signals=False, setup_logs=False)

        with patch("laneswap.server.start_health_server") as mock_start:
            mock_thread = Mock()
            mock_thread.join = Mock()
            mock_start.return_value = mock_thread

            # Run with immediate exit
            ls._running = False
            ls.run()

            mock_start.assert_called_once_with(ls)

    def test_run_with_main_func(
        self, free_port: int, clean_env: None
    ) -> None:
        """Test that run() executes main_func when provided."""
        ls = LaneSwap(port=free_port, setup_signals=False, setup_logs=False)

        main_func_called = []

        def main_func() -> None:
            main_func_called.append(True)
            ls._running = False  # Stop after first call

        with patch("laneswap.server.start_health_server") as mock_start:
            mock_thread = Mock()
            mock_thread.join = Mock()
            mock_start.return_value = mock_thread

            ls.run(main_func)

            assert main_func_called == [True]

    def test_run_executes_shutdown_handlers_on_exit(
        self, free_port: int, clean_env: None
    ) -> None:
        """Test that run() executes shutdown handlers when exiting."""
        ls = LaneSwap(port=free_port, setup_signals=False, setup_logs=False)

        shutdown_called = []

        @ls.on_shutdown
        def cleanup() -> None:
            shutdown_called.append(True)

        with patch("laneswap.server.start_health_server") as mock_start:
            mock_thread = Mock()
            mock_thread.join = Mock()
            mock_start.return_value = mock_thread

            ls._running = False
            ls.run()

            assert shutdown_called == [True]

    def test_run_handles_keyboard_interrupt(
        self, free_port: int, clean_env: None
    ) -> None:
        """Test that run() handles KeyboardInterrupt gracefully."""
        ls = LaneSwap(port=free_port, setup_signals=False, setup_logs=False)

        def main_func() -> None:
            raise KeyboardInterrupt()

        with patch("laneswap.server.start_health_server") as mock_start:
            mock_thread = Mock()
            mock_thread.join = Mock()
            mock_start.return_value = mock_thread

            # Should not raise exception
            ls.run(main_func)

            assert ls._running is False


class TestLaneSwapStartServer:
    """Test LaneSwap start_server() method."""

    def test_start_server_returns_thread(
        self, free_port: int, clean_env: None
    ) -> None:
        """Test that start_server() returns a thread."""
        ls = LaneSwap(port=free_port, setup_signals=False, setup_logs=False)

        with patch("laneswap.server.start_health_server") as mock_start:
            mock_thread = Mock()
            mock_start.return_value = mock_thread

            result = ls.start_server()

            assert result is mock_thread
            mock_start.assert_called_once_with(ls)

    def test_start_server_logs_message(
        self, free_port: int, clean_env: None
    ) -> None:
        """Test that start_server() logs startup message."""
        ls = LaneSwap(port=free_port, setup_signals=False, setup_logs=False)
        ls._logger = Mock()

        with patch("laneswap.server.start_health_server") as mock_start:
            mock_thread = Mock()
            mock_start.return_value = mock_thread

            ls.start_server()

            ls._logger.info.assert_called()


class TestLaneSwapIntegration:
    """Integration tests for LaneSwap class."""

    def test_full_lifecycle_with_health_checks(
        self, free_port: int, clean_env: None
    ) -> None:
        """Test complete lifecycle with health checks and shutdown."""
        ls = LaneSwap(
            service_name="test-service",
            port=free_port,
            setup_signals=False,
            setup_logs=False,
        )

        health_check_called = []
        shutdown_called = []

        @ls.health_check
        def check() -> Dict[str, str]:
            health_check_called.append(True)
            return {"component": "ok"}

        @ls.on_shutdown
        def cleanup() -> None:
            shutdown_called.append(True)

        # Verify health check works
        result = ls.health_registry.run_checks()
        assert result == {"component": "ok"}
        assert health_check_called == [True]

        # Verify shutdown handler works
        ls._execute_shutdown_handlers()
        assert shutdown_called == [True]

    def test_multiple_health_checks_and_handlers(
        self, clean_env: None, disable_signal_handlers: None
    ) -> None:
        """Test with multiple health checks and shutdown handlers."""
        ls = LaneSwap(setup_signals=False, setup_logs=False)

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

        @ls.health_check
        def check2() -> Dict[str, str]:
            return {"check2": "ok"}

        @ls.on_shutdown
        def handler1() -> None:
            pass

        @ls.on_shutdown
        def handler2() -> None:
            pass

        assert len(ls._health_registry._checks) == 2
        assert len(ls._shutdown_handlers) == 2

        results = ls.health_registry.run_checks()
        assert results == {"check1": "ok", "check2": "ok"}
