from __future__ import annotations

import pytest
from fastapi.testclient import TestClient

from app.core import settings as settings_mod
from app.main import _is_host_request, _is_loopback, app

MOBILE_UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) Mobile/15E148"


@pytest.mark.parametrize(
    "host,expected",
    [
        ("127.0.0.1", True),
        ("::1", True),
        ("localhost", True),
        ("::ffff:127.0.0.1", True),
        ("127.0.1.1", True),
        ("192.168.1.14", False),
        ("10.0.0.5", False),
        ("", False),
        (None, False),
    ],
)
def test_is_loopback(host, expected):
    assert _is_loopback(host) is expected


def test_host_request_recognizes_own_lan_ip(monkeypatch):
    # The host reaching itself via its LAN address must count as local, so
    # turning network access off never cuts the host off from its own server.
    monkeypatch.setattr("app.main._local_ips", lambda: frozenset({"192.168.1.14"}))
    assert _is_host_request("192.168.1.14") is True  # the host's own IP
    assert _is_host_request("127.0.0.1") is True  # loopback
    assert _is_host_request("192.168.1.99") is False  # a different device


def test_default_is_off_in_desktop_mode(monkeypatch):
    # Desktop keeps network off; the user opts in via the UI toggle.
    monkeypatch.delenv("STEMDECK_ALLOW_NETWORK", raising=False)
    monkeypatch.setenv("STEMDECK_DESKTOP", "1")
    assert settings_mod._default_allow_network() is False


def test_default_is_on_in_server_mode(monkeypatch):
    # Server/Docker deployments open the gate by default.
    monkeypatch.delenv("STEMDECK_ALLOW_NETWORK", raising=False)
    monkeypatch.delenv("STEMDECK_DESKTOP", raising=False)
    assert settings_mod._default_allow_network() is True


def test_env_var_pre_enables(monkeypatch):
    monkeypatch.setenv("STEMDECK_ALLOW_NETWORK", "1")
    assert settings_mod._default_allow_network() is True


def test_runtime_settings_round_trip_and_clamp():
    with TestClient(app) as c:
        r = c.post("/api/settings", json={"max_duration_sec": 600, "video_max_height": 1080})
        assert r.status_code == 200
        body = r.json()
        assert body["max_duration_sec"] == 600
        assert body["video_max_height"] == 1080
        # GET reflects the new values.
        assert c.get("/api/settings").json()["max_duration_sec"] == 600

    # Out-of-range values are clamped, not rejected.
    assert settings_mod.set_max_duration_sec(5) == 60  # floor
    assert settings_mod.set_max_duration_sec(99999) == 1200  # ceiling = 20 min
    assert settings_mod.set_video_max_height(99999) == 2160  # ceil


def test_port_default_and_clamp():
    assert settings_mod.get_port() == 8000  # default
    with TestClient(app) as c:
        assert c.post("/api/settings", json={"port": 9000}).json()["port"] == 9000
    assert settings_mod.set_port(80) == 1024  # floor (privileged ports rejected)
    assert settings_mod.set_port(70000) == 65535  # ceil


def test_settings_reject_non_integer():
    with TestClient(app) as c:
        assert c.post("/api/settings", json={"max_duration_sec": "abc"}).status_code == 422


def test_gate_blocks_non_loopback_when_off():
    settings_mod.set_allow_network(False)
    # TestClient's client host ("testclient") is treated as non-loopback.
    with TestClient(app) as c:
        r = c.get("/", headers={"user-agent": MOBILE_UA})
    assert r.status_code == 403


def test_gate_allows_everyone_when_on():
    settings_mod.set_allow_network(True)
    with TestClient(app) as c:
        assert c.get("/api/health").status_code == 200


def test_loopback_always_allowed_even_when_off(monkeypatch):
    settings_mod.set_allow_network(False)
    monkeypatch.setattr("app.main._is_loopback", lambda _host: True)
    with TestClient(app) as c:
        assert c.get("/api/health").status_code == 200


def test_post_toggles_off_then_blocks():
    settings_mod.set_allow_network(True)  # so the non-loopback client can reach POST
    with TestClient(app) as c:
        r = c.post("/api/settings", json={"allow_network": False})
        assert r.status_code == 200
        assert r.json()["allow_network"] is False
    # Now off → a non-loopback client is blocked from everything.
    with TestClient(app) as c:
        assert c.get("/api/settings").status_code == 403
