"""Discord daily-feed post: embed shape, webhook delivery, and the once-a-day guard.
No network — the shared client's .post is stubbed, like the IndexNow tests."""
import datetime
import time

import pytest

from notifications import discord
from notifications import notify


def _feed(ranked, generated_at=None, date=None):
    # Fresh by default so it passes homepage.is_stale(); tests that assert on the
    # embed timestamp pin generated_at explicitly.
    return {
        "generated_at": generated_at if generated_at is not None else time.time(),
        "date": date or datetime.date.today().isoformat(),
        "ranked": ranked,
    }


_CARD_HOT = {
    "display": "Tehran, Iran", "name": "Tehran", "metric": "tmax",
    "metric_label": "High temp", "value": 104.2, "normal": 96.8,
    "percentile": 99.0, "pct_label": "99th percentile", "grade": "Near Record",
    "grade_label": "Near Record hot", "tail": "warm", "date_label": None,
}
_CARD_COLD = {
    "display": "Oslo, Norway", "name": "Oslo", "metric": "tmin",
    "metric_label": "Low temp", "value": 5.0, "normal": 20.0,
    "percentile": 3.0, "pct_label": "3rd percentile", "grade": "Near Record",
    "grade_label": "Near Record cold", "tail": "cold", "date_label": "Jul 18",
}


def test_build_embed_has_numbers_and_cities():
    embed = discord.build_embed(_feed([_CARD_HOT, _CARD_COLD], generated_at=1_700_000_000.0))
    assert "unusual weather" in embed["title"].lower()
    desc = embed["description"]
    assert "Tehran, Iran" in desc and "Oslo, Norway" in desc
    # Real readings, in both units, with the normal for context.
    assert "104°F (40°C)" in desc
    assert "normal 97°F" in desc
    # The percentile + grade tag rides along.
    assert "99th percentile" in desc and "Near Record hot" in desc
    # A non-today card is dated so it can't pass for "right now".
    assert "(Jul 18)" in desc
    # Warm top card => warm accent; timestamp from the feed's build time.
    assert embed["color"] == discord._HOT
    assert embed["timestamp"].startswith("2023-11-14")


def test_embed_accent_follows_the_top_cards_tail():
    assert discord.build_embed(_feed([_CARD_COLD]))["color"] == discord._COLD
    assert discord.build_embed(_feed([_CARD_HOT]))["color"] == discord._HOT


def test_build_embed_rejects_empty_feed():
    with pytest.raises(ValueError):
        discord.build_embed(_feed([]))


def test_post_disabled_without_webhook(monkeypatch):
    monkeypatch.setattr(discord, "WEBHOOK_URL", "")
    posted = []
    monkeypatch.setattr(discord._client, "post", lambda *a, **k: posted.append(a) or None)
    assert discord.post_daily_feed(_feed([_CARD_HOT])) is False
    assert posted == []   # never touched the network


class _Resp:
    def __init__(self, code): self.status_code = code


def test_post_sends_embed_to_the_webhook(monkeypatch):
    monkeypatch.setattr(discord, "WEBHOOK_URL", "https://discord.test/webhook/abc")
    calls = []
    monkeypatch.setattr(discord._client, "post",
                        lambda url, json=None, **k: (calls.append((url, json)), _Resp(204))[1])
    assert discord.post_daily_feed(_feed([_CARD_HOT])) is True
    (url, payload), = calls
    assert url == "https://discord.test/webhook/abc"
    assert payload["username"] == "Thermograph"
    assert payload["embeds"][0]["description"].startswith("**Tehran, Iran**")


def test_post_also_broadcasts_to_weather_channel(monkeypatch):
    # No webhook, but a bot + weather channel configured -> digest goes to the
    # channel via the bot REST API.
    monkeypatch.setattr(discord, "WEBHOOK_URL", "")
    monkeypatch.setattr(discord, "BOT_TOKEN", "bot-abc")
    monkeypatch.setattr(discord, "WEATHER_CHANNEL_ID", "chan-weather")
    calls = []
    monkeypatch.setattr(discord._client, "post",
                        lambda url, json=None, **k: (calls.append((url, json)), _Resp(204))[1])
    assert discord.post_daily_feed(_feed([_CARD_HOT])) is True
    (url, body), = calls
    assert url.endswith("/channels/chan-weather/messages")
    assert body["embeds"][0]["description"].startswith("**Tehran, Iran**")


def test_post_is_best_effort_on_http_error(monkeypatch):
    monkeypatch.setattr(discord, "WEBHOOK_URL", "https://discord.test/webhook/abc")

    def _boom(*a, **k):
        raise RuntimeError("network down")
    monkeypatch.setattr(discord._client, "post", _boom)
    # Never raises; just reports failure.
    assert discord.post_daily_feed(_feed([_CARD_HOT])) is False


def test_post_skips_stale_or_empty_feed(monkeypatch):
    monkeypatch.setattr(discord, "WEBHOOK_URL", "https://discord.test/webhook/abc")
    monkeypatch.setattr(discord._client, "post", lambda *a, **k: _Resp(204))
    # Yesterday's feed is stale -> no post.
    yesterday = (datetime.date.today() - datetime.timedelta(days=1)).isoformat()
    assert discord.post_daily_feed(_feed([_CARD_HOT], date=yesterday)) is False
    # Empty ranked -> no post.
    assert discord.post_daily_feed(_feed([])) is False


def test_daily_guard_posts_once_per_day(monkeypatch):
    monkeypatch.setattr(notify, "_last_discord_post_day", None)
    monkeypatch.setattr(discord, "enabled", lambda: True)
    posts = []
    monkeypatch.setattr(discord, "post_daily_feed", lambda: posts.append(1) or True)

    day1 = datetime.date(2026, 7, 19)
    day2 = datetime.date(2026, 7, 20)

    class _D(datetime.date):
        _t = day1
        @classmethod
        def today(cls): return cls._t
    monkeypatch.setattr(notify.datetime, "date", _D)

    notify._maybe_post_discord()
    notify._maybe_post_discord()      # same day -> still one post
    assert posts == [1]

    _D._t = day2
    notify._maybe_post_discord()      # new day -> a second post
    assert posts == [1, 1]


def test_daily_guard_retries_after_a_failed_post(monkeypatch):
    monkeypatch.setattr(notify, "_last_discord_post_day", None)
    monkeypatch.setattr(discord, "enabled", lambda: True)
    outcomes = iter([False, True])
    monkeypatch.setattr(discord, "post_daily_feed", lambda: next(outcomes))
    # First call fails -> day not marked done -> second call retries and succeeds.
    notify._maybe_post_discord()
    notify._maybe_post_discord()
    assert notify._last_discord_post_day == datetime.date.today().isoformat()
