"""Discord HTTP-interactions: Ed25519 verification, PING/PONG, /grade dispatch,
and the wired FastAPI route. No live Discord — we hold a test signing key and sign
requests ourselves."""
import datetime
import json

import pytest
from fastapi.testclient import TestClient
from nacl.signing import SigningKey

from api import homepage
from web import app as appmod
from notifications import discord_interactions as di

B = "/thermograph"

_SK = SigningKey.generate()
_PUBKEY = _SK.verify_key.encode().hex()


def _sign(body: bytes, ts: str = "1700000000") -> tuple[str, str]:
    return _SK.sign(ts.encode() + body).signature.hex(), ts


_CARD = {
    "display": "Tokyo, Japan", "name": "Tokyo", "slug": "tokyo-jp",
    "metric_label": "High temp", "value": 96.0, "normal": 84.0,
    "pct_label": "97th percentile", "grade_label": "Very High",
    "tail": "warm", "date_label": None,
}


# --- signature verification --------------------------------------------------

def test_verify_accepts_a_good_signature():
    body = b'{"type":1}'
    sig, ts = _sign(body)
    assert di.verify(sig, ts, body, _PUBKEY) is True


def test_verify_rejects_tampering_and_bad_inputs():
    body = b'{"type":1}'
    sig, ts = _sign(body)
    assert di.verify(sig, ts, body + b" ", _PUBKEY) is False   # body changed
    assert di.verify(sig, "1700000001", body, _PUBKEY) is False  # timestamp changed
    assert di.verify("00" * 64, ts, body, _PUBKEY) is False       # wrong signature
    assert di.verify("nothex", ts, body, _PUBKEY) is False         # malformed
    assert di.verify(sig, ts, body, "") is False                   # no key configured
    assert di.verify("", ts, body, _PUBKEY) is False               # no signature


# --- handle(): routing -------------------------------------------------------

def test_handle_ping_pongs(monkeypatch):
    monkeypatch.setattr(di, "PUBLIC_KEY", _PUBKEY)
    body = b'{"type":1}'
    sig, ts = _sign(body)
    status, payload = di.handle(body, sig, ts)
    assert status == 200 and payload == {"type": 1}


def test_handle_rejects_bad_signature(monkeypatch):
    monkeypatch.setattr(di, "PUBLIC_KEY", _PUBKEY)
    body = b'{"type":1}'
    status, payload = di.handle(body, "00" * 64, "1700000000")
    assert status == 401


def test_grade_command_returns_the_citys_grade(monkeypatch):
    monkeypatch.setattr(di, "PUBLIC_KEY", _PUBKEY)
    monkeypatch.setattr(homepage, "_grade_city", lambda city, today: dict(_CARD))
    body = json.dumps({"type": 2, "data": {
        "name": "grade", "options": [{"name": "city", "value": "Tokyo"}]}}).encode()
    sig, ts = _sign(body)
    status, payload = di.handle(body, sig, ts)
    assert status == 200 and payload["type"] == 4
    embed = payload["data"]["embeds"][0]
    assert "Tokyo, Japan" in embed["title"]
    assert "96°F" in embed["description"] and "97th percentile" in embed["description"]
    assert embed["url"].endswith("/climate/tokyo-jp")


def test_grade_unknown_city_is_ephemeral(monkeypatch):
    monkeypatch.setattr(di, "PUBLIC_KEY", _PUBKEY)
    data = di._grade_message("Nowherecityville")
    assert data["flags"] == di._EPHEMERAL and "don't track" in data["content"]


def test_grade_city_not_yet_warm_is_ephemeral(monkeypatch):
    monkeypatch.setattr(homepage, "_grade_city", lambda city, today: None)
    data = di._grade_message("Tokyo")           # a real curated city, but no warm cache
    assert data["flags"] == di._EPHEMERAL and "No recent reading" in data["content"]


def test_resolve_city_prefers_exact_then_population():
    # Real cities.json: an exact name resolves, and it's a real curated entry.
    tokyo = di._resolve_city("tokyo")
    assert tokyo is not None and tokyo["name"] == "Tokyo"
    assert di._resolve_city("   ") is None
    assert di._resolve_city("Zzznotacity") is None


# --- the wired FastAPI route -------------------------------------------------

def test_interactions_route_pings_and_rejects(monkeypatch):
    monkeypatch.setattr(di, "PUBLIC_KEY", _PUBKEY)
    client = TestClient(appmod.app)
    body = b'{"type":1}'
    sig, ts = _sign(body)
    # Signed PING -> PONG. Send raw bytes so the signature matches byte-for-byte.
    r = client.post(f"{B}/discord/interactions", content=body,
                    headers={"x-signature-ed25519": sig, "x-signature-timestamp": ts})
    assert r.status_code == 200 and r.json() == {"type": 1}
    # Unsigned -> 401 (Discord's security probe).
    r = client.post(f"{B}/discord/interactions", content=body)
    assert r.status_code == 401
