"""Unit tests for handler/load.py"""

import threading
import pytest
from unittest.mock import patch, MagicMock

_mock_env = MagicMock()
_mock_env.model.concurrency = 4

with patch("gmi_ieops.handler.load.env", _mock_env):
    from gmi_ieops.handler.load import (
        LoadCalculator, DefaultLoadCalculator,
        get_load_calculator, set_load_calculator, calculate_load,
    )
    import gmi_ieops.handler.load as _mod
_mod.env = _mock_env


class TestDefaultLoadCalculator:

    def test_request_lifecycle(self):
        c = DefaultLoadCalculator(max_concurrency=4)
        assert c.active_requests == 0 and c.calculate() == 0.0

        c.on_request_start()
        assert c.calculate() == pytest.approx(0.25)
        c.on_request_start()
        assert c.calculate() == pytest.approx(0.5)
        c.on_request_end()
        assert c.calculate() == pytest.approx(0.25)

    def test_boundaries(self):
        c = DefaultLoadCalculator(max_concurrency=2)
        c.on_request_start(); c.on_request_start()
        assert c.calculate() == pytest.approx(1.0)
        for _ in range(3): c.on_request_start()
        assert c.calculate() == pytest.approx(2.5)

    def test_end_does_not_go_negative(self):
        c = DefaultLoadCalculator(max_concurrency=4)
        c.on_request_end(); c.on_request_end()
        assert c.active_requests == 0

    def test_max_concurrency_setter(self):
        c = DefaultLoadCalculator(max_concurrency=4)
        c.max_concurrency = 8
        assert c.max_concurrency == 8
        c.max_concurrency = 0
        assert c.max_concurrency == 1

    def test_negative_max_concurrency_clamped(self):
        c = DefaultLoadCalculator(max_concurrency=-5)
        assert c.max_concurrency == 1
        c.max_concurrency = -100
        assert c.max_concurrency == 1

    def test_thread_safety(self):
        c = DefaultLoadCalculator(max_concurrency=100)
        def worker():
            for _ in range(1000):
                c.on_request_start()
            for _ in range(1000):
                c.on_request_end()
        threads = [threading.Thread(target=worker) for _ in range(10)]
        for t in threads: t.start()
        for t in threads: t.join()
        assert c.active_requests == 0


class TestGlobalSingleton:

    def setup_method(self):
        _mod._calculator = None

    def test_singleton_and_set(self):
        c1 = get_load_calculator()
        assert c1 is get_load_calculator()
        assert isinstance(c1, DefaultLoadCalculator)

        class Custom(LoadCalculator):
            def calculate(self): return 0.99
        set_load_calculator(Custom())
        assert calculate_load() == pytest.approx(0.99)
