"""Site-wide banner display for aablocks.

The Data Portal API exposes active banners (maintenance windows,
announcements, outages) at GET /banners. The first API call of each
process fetches them and prints each banner to stderr once, so users
see active notices without polluting stdout (e.g. piped CSV output).

Each banner is shown at most once per calendar day: the date a banner id
was last printed is persisted to ~/.aab/banners.json, so repeated
commands the same day stay quiet while a newly-posted banner still shows
the day it appears.

Banners only appear when stderr is an interactive terminal, and can be
disabled with `aablocks config banners false`.

Banner display must never break SDK usage: all failures are swallowed
(after logging), and the fetch is not retried within the process.
"""

from __future__ import annotations

import functools
import json
import sys
import time
from datetime import date

import requests

from .config import CONFIG_DIR, get_banners_enabled, get_base_url, write_secure_file
from .logging import log_request

SEEN_FILE_PATH = CONFIG_DIR / "banners.json"


@functools.cache
def show_banners_once() -> None:
    """Display active banners on stderr, at most once per process.

    Best-effort and fully isolated: every exception is swallowed so banner
    display can never break a CLI command or SDK call — not a corrupt
    config, an unwritable log/state file, nor a broken stderr pipe. Because
    the call always completes, `functools.cache` memoizes it and a failed
    attempt is never retried within the process.
    """
    # One catch-all guard: banner display is best-effort and must never
    # propagate (the inner try/except below still logs fetch failures).
    try:
        if _banners_suppressed():
            return

        start_time = time.time()
        error_msg = None
        status_code = None
        try:
            resp = requests.get(f"{get_base_url()}/banners", timeout=5)
            status_code = resp.status_code
            resp.raise_for_status()
            banners = resp.json()
        except Exception as e:
            error_msg = str(e)
            return
        finally:
            latency_ms = int((time.time() - start_time) * 1000)
            log_request("GET", "/banners", None, status_code, error_msg, latency_ms)

        with_message = [b for b in banners if isinstance(b, dict) and b.get("message")]
        if not with_message:
            return

        seen = _load_seen()
        today = _today()
        fresh = [b for b in with_message if seen.get(str(b["id"])) != today]
        if not fresh:
            return

        print(file=sys.stderr)
        for banner in fresh:
            kind = banner.get("kind") or "announcement"
            print(f"aablocks: [{kind}] {banner['message']}", file=sys.stderr)
        print(file=sys.stderr)

        # Keep an entry for every currently-active banner (pruning ids no
        # longer returned), stamping today on the ones we just showed.
        new_seen = {str(b["id"]): seen.get(str(b["id"]), today) for b in with_message}
        for banner in fresh:
            new_seen[str(banner["id"])] = today
        write_secure_file(SEEN_FILE_PATH, new_seen)
    except Exception:
        # Do nothing on purpose: banner display is best-effort, so we never
        # let a failure here prevent the actual command from succeeding.
        pass


def _banners_suppressed() -> bool:
    """Return True when banner display is disabled for this invocation."""
    if not sys.stderr.isatty():
        return True
    return not get_banners_enabled()


def _today() -> str:
    """Return today's local date as an ISO string (e.g. '2026-06-05')."""
    return date.today().isoformat()


def _load_seen() -> dict[str, str]:
    """Map of banner key -> ISO date last shown. Empty dict on any error."""
    try:
        with open(SEEN_FILE_PATH) as f:
            data = json.load(f)
    except (OSError, ValueError):
        return {}
    return data if isinstance(data, dict) else {}
