"""Tests for /api/v1/preferences/notifications endpoints."""

import pytest

from ledgrab.config import get_config


@pytest.fixture(scope="module")
def client():
    """TestClient with auth header — same pattern as test_preferences_api.py."""
    from fastapi.testclient import TestClient

    from ledgrab.main import app

    api_key = next(iter(get_config().auth.api_keys.values()), "")
    with TestClient(app, raise_server_exceptions=False) as c:
        if api_key:
            c.headers["Authorization"] = f"Bearer {api_key}"
        yield c


def _full_prefs() -> dict:
    return {
        "channels": {
            "device_online": "snack",
            "device_offline": "both",
            "device_discovered": "os",
            "device_lost": "none",
        },
        "background_discovery_enabled": True,
        "startup_grace_sec": 15,
        "flap_debounce_sec": 7,
    }


def test_get_returns_defaults_when_unset(client):
    """When no prefs have been saved, GET returns the documented defaults."""
    # Wipe the stored row to a falsy value so this test is independent of
    # any prior test in the suite that may have PUT a customised matrix.
    # `load_notification_preferences` falls back to schema defaults when
    # the stored value is empty / falsy.
    from ledgrab.api.dependencies import get_database

    db = get_database()
    db.set_setting("notification_preferences", {})

    resp = client.get("/api/v1/preferences/notifications")
    assert resp.status_code == 200
    body = resp.json()
    assert body["background_discovery_enabled"] is True
    assert body["startup_grace_sec"] == 10
    assert body["flap_debounce_sec"] == 5
    # Default channel matrix
    assert body["channels"]["device_online"] == "snack"
    assert body["channels"]["device_offline"] == "both"
    assert body["channels"]["device_discovered"] == "snack"
    assert body["channels"]["device_lost"] == "none"


def test_put_then_get_round_trips(client):
    """PUT a payload, GET it back unchanged."""
    payload = _full_prefs()
    put = client.put("/api/v1/preferences/notifications", json=payload)
    assert put.status_code == 200
    assert put.json()["startup_grace_sec"] == 15

    got = client.get("/api/v1/preferences/notifications")
    assert got.status_code == 200
    assert got.json() == payload


def test_put_rejects_invalid_channel(client):
    """A bogus channel value (e.g. 'siren') is rejected by Pydantic."""
    bad = _full_prefs()
    bad["channels"]["device_offline"] = "siren"
    resp = client.put("/api/v1/preferences/notifications", json=bad)
    assert resp.status_code == 422


def test_put_rejects_grace_out_of_range(client):
    """startup_grace_sec is clamped to [0, 300]."""
    bad = _full_prefs()
    bad["startup_grace_sec"] = -5
    assert client.put("/api/v1/preferences/notifications", json=bad).status_code == 422

    bad["startup_grace_sec"] = 9999
    assert client.put("/api/v1/preferences/notifications", json=bad).status_code == 422


def test_put_rejects_debounce_out_of_range(client):
    """flap_debounce_sec is clamped to [0, 60]."""
    bad = _full_prefs()
    bad["flap_debounce_sec"] = 999
    assert client.put("/api/v1/preferences/notifications", json=bad).status_code == 422


def test_partial_payload_uses_defaults_for_omitted_channels(client):
    """Pydantic fills in default channels when the matrix is partial.

    The frontend may want to PUT only what changed; the backend should
    fill in the default channel matrix for omitted rows so we don't
    silently lose user preferences via partial-write.
    """
    partial = {"background_discovery_enabled": False}
    resp = client.put("/api/v1/preferences/notifications", json=partial)
    assert resp.status_code == 200
    body = resp.json()
    assert body["background_discovery_enabled"] is False
    # Defaulted matrix is present
    assert body["channels"]["device_offline"] == "both"


def test_corrupt_stored_value_falls_back_to_defaults(client):
    """If something stomps on the stored row, the GET handler must
    return defaults instead of 500. Mirrors how load_shutdown_action
    treats corrupt input."""
    # Stuff garbage into the underlying setting via the same Database
    # the route uses, then verify GET still works.
    from ledgrab.api.dependencies import get_database

    db = get_database()
    db.set_setting("notification_preferences", {"channels": "totally-wrong"})

    resp = client.get("/api/v1/preferences/notifications")
    assert resp.status_code == 200
    # Defaults restored
    assert resp.json()["channels"]["device_offline"] == "both"
