import random

from pypos.modules.auth.services.login_network_policy_service import LoginNetworkPolicyService
from pypos.modules.auth.services.network_probe_service import NetworkProbeService


class _FakeProbeService:
    def __init__(self, snapshots):
        self._snapshots = list(snapshots or [])
        self._index = 0

    def probe_network_snapshot(self, policy=None):
        if not self._snapshots:
            return {
                "raw_state": NetworkProbeService.RAW_OFFLINE,
                "server_online": False,
                "internet_online": False,
                "server": {},
                "internet": {},
            }
        if self._index >= len(self._snapshots):
            return dict(self._snapshots[-1])
        data = dict(self._snapshots[self._index])
        self._index += 1
        return data


class _Clock:
    def __init__(self, start=1000.0):
        self._now = float(start)

    def __call__(self):
        return float(self._now)

    def add(self, sec):
        self._now += float(sec)


def _policy(**override):
    base = {
        "enabled": True,
        "require_online": True,
        "show_startup_notice": True,
        "disable_button_when_blocked": True,
        "recheck_interval_ms": 3000,
        "fail_threshold": 3,
        "recover_threshold": 2,
        "grace_seconds": 45,
        "allow_server_down_but_internet_up": True,
        "allow_network_unstable": True,
        "offline_emergency_admin_enabled": False,
    }
    base.update(override)
    return base


def _snap(raw_state, server_online=False, internet_online=False):
    return {
        "raw_state": raw_state,
        "server_online": bool(server_online),
        "internet_online": bool(internet_online),
        "server": {},
        "internet": {},
    }


def test_offline_blocked_when_require_online():
    probe = _FakeProbeService(
        [_snap(NetworkProbeService.RAW_OFFLINE, False, False)]
    )
    clock = _Clock()
    service = LoginNetworkPolicyService(
        probe_service=probe,
        policy_config_getter=lambda: _policy(),
        now_fn=clock,
    )

    result = service.evaluate()
    assert result["state"] == LoginNetworkPolicyService.STATE_OFFLINE
    assert result["allow_login"] is False
    assert result["block_login"] is True


def test_internet_only_allowed_when_server_down_but_internet_up():
    probe = _FakeProbeService(
        [
            _snap(NetworkProbeService.RAW_INTERNET_ONLY, False, True),
            _snap(NetworkProbeService.RAW_INTERNET_ONLY, False, True),
            _snap(NetworkProbeService.RAW_INTERNET_ONLY, False, True),
        ]
    )
    clock = _Clock()
    service = LoginNetworkPolicyService(
        probe_service=probe,
        policy_config_getter=lambda: _policy(fail_threshold=2),
        now_fn=clock,
    )

    first = service.evaluate()
    second = service.evaluate()
    third = service.evaluate()

    assert first["allow_login"] is True
    assert second["state"] == LoginNetworkPolicyService.STATE_ONLINE_INTERNET_ONLY
    assert second["allow_login"] is True
    assert third["allow_login"] is True


def test_grace_window_allows_temporary_offline_spike():
    probe = _FakeProbeService(
        [
            _snap(NetworkProbeService.RAW_SERVER_OK, True, False),
            _snap(NetworkProbeService.RAW_OFFLINE, False, False),
            _snap(NetworkProbeService.RAW_OFFLINE, False, False),
            _snap(NetworkProbeService.RAW_OFFLINE, False, False),
        ]
    )
    clock = _Clock()
    service = LoginNetworkPolicyService(
        probe_service=probe,
        policy_config_getter=lambda: _policy(fail_threshold=3, grace_seconds=20),
        now_fn=clock,
    )

    ok = service.evaluate()
    assert ok["allow_login"] is True

    clock.add(2)
    first_offline = service.evaluate()
    assert first_offline["allow_login"] is True
    assert first_offline["state"] == LoginNetworkPolicyService.STATE_ONLINE_SERVER_UNSTABLE

    clock.add(2)
    second_offline = service.evaluate()
    assert second_offline["allow_login"] is True

    clock.add(25)
    third_offline = service.evaluate()
    assert third_offline["allow_login"] is False
    assert third_offline["state"] == LoginNetworkPolicyService.STATE_OFFLINE


def test_disabled_policy_always_allows():
    probe = _FakeProbeService(
        [_snap(NetworkProbeService.RAW_OFFLINE, False, False)]
    )
    service = LoginNetworkPolicyService(
        probe_service=probe,
        policy_config_getter=lambda: _policy(enabled=False),
    )

    result = service.evaluate()
    assert result["state"] == LoginNetworkPolicyService.STATE_DISABLED
    assert result["allow_login"] is True
    assert result["block_login"] is False


def test_recovery_needs_minimum_success_streak():
    probe = _FakeProbeService(
        [
            _snap(NetworkProbeService.RAW_OFFLINE, False, False),
            _snap(NetworkProbeService.RAW_SERVER_OK, True, False),
            _snap(NetworkProbeService.RAW_SERVER_OK, True, False),
        ]
    )
    service = LoginNetworkPolicyService(
        probe_service=probe,
        policy_config_getter=lambda: _policy(recover_threshold=2),
    )

    service.evaluate()
    recovering = service.evaluate()
    stable = service.evaluate()

    assert recovering["state"] == LoginNetworkPolicyService.STATE_ONLINE_SERVER_UNSTABLE
    assert stable["state"] == LoginNetworkPolicyService.STATE_ONLINE_SERVER_STABLE


def test_stress_random_snapshot_no_crash():
    random.seed(42)
    states = [
        NetworkProbeService.RAW_SERVER_OK,
        NetworkProbeService.RAW_INTERNET_ONLY,
        NetworkProbeService.RAW_OFFLINE,
    ]
    snapshots = []
    for _ in range(4000):
        raw = random.choice(states)
        snapshots.append(
            _snap(
                raw_state=raw,
                server_online=(raw == NetworkProbeService.RAW_SERVER_OK),
                internet_online=(raw == NetworkProbeService.RAW_INTERNET_ONLY),
            )
        )

    probe = _FakeProbeService(snapshots)
    service = LoginNetworkPolicyService(
        probe_service=probe,
        policy_config_getter=lambda: _policy(),
    )

    for _ in range(4000):
        result = service.evaluate()
        assert isinstance(result, dict)
        assert "state" in result
        assert "allow_login" in result


def test_invalid_policy_types_are_normalized_safely():
    probe = _FakeProbeService(
        [_snap(NetworkProbeService.RAW_INTERNET_ONLY, False, True)]
    )
    service = LoginNetworkPolicyService(
        probe_service=probe,
        policy_config_getter=lambda: {
            "enabled": "yes",
            "require_online": "true",
            "disable_button_when_blocked": "abc",
            "recheck_interval_ms": "x",
            "fail_threshold": "0",
            "recover_threshold": "-3",
            "grace_seconds": "-10",
            "allow_server_down_but_internet_up": "1",
            "allow_network_unstable": "1",
        },
    )

    result = service.evaluate()
    policy = result["policy"]
    assert policy["enabled"] is True
    assert policy["require_online"] is True
    assert policy["recheck_interval_ms"] >= 500
    assert policy["fail_threshold"] >= 1
    assert policy["recover_threshold"] >= 1
    assert policy["grace_seconds"] >= 0
