"""
ntfy Notification Utility
=========================

Sends push notifications to an ntfy.sh topic.  Designed for use by Claude
(or any script) to push updates when human attention is needed.

Usage — command line
--------------------
    python tools/ntfy.py "Build complete"
    python tools/ntfy.py "OOS dashboard ready to review" --title "TradingBot"
    python tools/ntfy.py "3h BTC run finished — 94/107 TV parity" --priority high
    python tools/ntfy.py "Need TV re-export for COINBASE_ETHUSD" --tags "warning,chart"

Usage — imported in Python
--------------------------
    from tools.ntfy import notify

    notify("Run finished — check results")
    notify("Need TV re-export", title="Action Required", priority="high", tags=["warning"])

Configuration
-------------
Default topic: jlo_alerts  (matches run_marathon.py)
Override via:
  - NTFY_TOPIC environment variable
  - --topic flag on the CLI
  - topic= parameter in notify()

Character limits
----------------
  - Message body: ntfy.sh recommends ≤4096 bytes; longer messages are truncated
    automatically by this tool with a "…[truncated]" suffix.
  - Title:        ≤255 characters.
  - Tags:         each tag ≤64 characters, ≤5 tags per message.

Emoji / Unicode
---------------
ntfy.sh supports Unicode emoji in both title and body.  You can pass emoji
directly as Unicode characters.  ntfy also supports emoji shortcodes as tags
(e.g. --tags "white_check_mark,chart_with_upwards_trend") which the mobile
app renders as emoji in the notification.

Priority levels
---------------
  max / urgent    : bypasses Do-Not-Disturb
  high            : important, audible
  default         : normal (default)
  low             : silent
  min             : no notification, just badge update

See https://docs.ntfy.sh/ for full documentation.
"""

import argparse
import os
import subprocess
import sys

NTFY_TOPIC   = os.environ.get('NTFY_TOPIC', 'jlo_alerts')
NTFY_SERVER  = 'ntfy.sh'
MAX_BODY     = 4096
MAX_TITLE    = 255
MAX_TAGS     = 5
MAX_TAG_LEN  = 64

VALID_PRIORITIES = {'max', 'urgent', 'high', 'default', 'low', 'min'}


def _truncate(text: str, max_bytes: int, suffix: str = '…[truncated]') -> str:
    """Truncate text to fit within max_bytes UTF-8, appending suffix if cut."""
    encoded = text.encode('utf-8')
    if len(encoded) <= max_bytes:
        return text
    suffix_bytes = suffix.encode('utf-8')
    cut = encoded[:max_bytes - len(suffix_bytes)].decode('utf-8', errors='ignore')
    return cut + suffix


def _sanitise_tags(tags: list[str]) -> list[str]:
    """Trim to MAX_TAGS and truncate each tag."""
    result = []
    for t in tags[:MAX_TAGS]:
        t = t.strip()[:MAX_TAG_LEN]
        if t:
            result.append(t)
    return result


def notify(
    message:  str,
    title:    str | None  = None,
    priority: str         = 'default',
    tags:     list[str]   = None,
    topic:    str | None  = None,
    silent:   bool        = False,
) -> bool:
    """
    Send an ntfy push notification.

    Parameters
    ----------
    message  : Notification body text.
    title    : Optional title (shown in bold).  Defaults to topic name.
    priority : One of max/urgent/high/default/low/min.
    tags     : List of tag strings or emoji shortcodes (e.g. ['white_check_mark']).
    topic    : ntfy topic override.  Falls back to NTFY_TOPIC env var / 'jlo_alerts'.
    silent   : If True, suppress console output.

    Returns
    -------
    True if curl returned exit code 0, False otherwise.
    """
    topic    = topic or NTFY_TOPIC
    priority = priority if priority in VALID_PRIORITIES else 'default'
    tags     = _sanitise_tags(tags or [])

    body  = _truncate(message, MAX_BODY)
    title = _truncate(title, MAX_TITLE) if title else None

    cmd = ['curl', '-s', '--max-time', '10']
    if title:
        cmd += ['-H', f'Title: {title}']
    if priority != 'default':
        cmd += ['-H', f'Priority: {priority}']
    if tags:
        cmd += ['-H', f'Tags: {",".join(tags)}']
    cmd += ['-d', body, f'{NTFY_SERVER}/{topic}']

    try:
        result = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
        success = result.returncode == 0
    except (subprocess.TimeoutExpired, FileNotFoundError) as exc:
        if not silent:
            print(f'[ntfy] ERROR: {exc}', file=sys.stderr)
        return False

    if not silent:
        status = 'sent' if success else f'FAILED (rc={result.returncode})'
        t_str  = f' | title: {title}' if title else ''
        p_str  = f' | priority: {priority}' if priority != 'default' else ''
        print(f'[ntfy] {status} → {topic}{t_str}{p_str}')
        if not success and result.stderr:
            print(f'       stderr: {result.stderr.strip()}', file=sys.stderr)

    return success


def main():
    parser = argparse.ArgumentParser(
        description='Send an ntfy push notification',
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog=__doc__,
    )
    parser.add_argument('message',
                        help='Notification body text')
    parser.add_argument('--title',    default=None,
                        help='Notification title (default: topic name)')
    parser.add_argument('--priority', default='default',
                        choices=list(VALID_PRIORITIES),
                        help='Priority level (default: default)')
    parser.add_argument('--tags',     default='',
                        help='Comma-separated tags or emoji shortcodes '
                             '(e.g. "white_check_mark,chart_with_upwards_trend")')
    parser.add_argument('--topic',    default=None,
                        help=f'ntfy topic override (default: {NTFY_TOPIC})')
    args = parser.parse_args()

    tags = [t.strip() for t in args.tags.split(',') if t.strip()] if args.tags else []

    success = notify(
        message  = args.message,
        title    = args.title,
        priority = args.priority,
        tags     = tags,
        topic    = args.topic,
    )
    sys.exit(0 if success else 1)


if __name__ == '__main__':
    main()
