#!/usr/bin/env python3
"""
notify_forecast.py — Telegram alert when the daily forecast is built.

Sends the full vol & range block to every dashboard-token holder, each
with their personal link to the forecast page. Sends once per session
(state file: data\\logs\\forecast_notified.json) — the dashboard server
calls this after every successful build, so retries are no-ops.

Manual use:
    python src\\execution\\notify_forecast.py                (normal — once per session)
    python src\\execution\\notify_forecast.py --force        (resend to everyone now)
    python src\\execution\\notify_forecast.py --to CHAT_ID   (test: that chat only, no state change)

Bot token: uses the TELEGRAM_TOKEN environment variable if set, otherwise
reads the same constant straight out of live_signal_monitor_v3.py — one
source of truth, no credentials duplicated in this file.
"""
import json
import re
import ssl
import sys
import time
import urllib.parse
import urllib.request
from datetime import datetime, timezone
from pathlib import Path

BASE_PATH = Path(__file__).resolve().parents[2]
LOG_DIR = BASE_PATH / "data" / "logs"
FORECAST_JSON = BASE_PATH / "forecast_data.json"
TOKENS_FILE = LOG_DIR / "dashboard_tokens.json"
STATE_FILE = LOG_DIR / "forecast_notified.json"
MONITOR_FILE = BASE_PATH / "src" / "execution" / "live_signal_monitor_v3.py"
FORECAST_PAGE_URL = "https://bennettsdashboard.com/forecast.html"


def bot_token():
    import os
    tok = os.getenv("TELEGRAM_TOKEN", "").strip()
    if tok:
        return tok
    try:
        src = MONITOR_FILE.read_text(encoding="utf-8", errors="replace")
        m = re.search(
            r'TELEGRAM_TOKEN\s*=\s*os\.getenv\(\s*"TELEGRAM_TOKEN"\s*,\s*"([^"]+)"',
            src)
        if m:
            return m.group(1).strip()
    except Exception:
        pass
    return ""


def load_json(path, default):
    try:
        with open(path, encoding="utf-8") as f:
            return json.load(f)
    except Exception:
        return default


def instrument_block(d):
    name = d.get("display", "?")
    lines = [f"──── {name} " + "─" * max(4, 21 - len(name))]
    if d.get("event_conditioned"):
        lines.append(f"⚠️ {d['label']} day — event-conditioned bands")
    lines.append(f"Volatility (annualized) : {d['ann_vol']:.2f}%")
    lines.append(f"High to Low range       : {d['hl_med']:.2f}% median · "
                 f"{d['hl_75']:.2f}% 75th Percentile")
    lines.append(f"Open to Close move      : {d['oc_med']:.2f}% median · "
                 f"{d['oc_75']:.2f}% 75th Percentile")
    return "\n".join(lines)


def compose(data):
    parts = ["📊 VOL & RANGE FORECAST",
             f"For session: {data.get('session_label', '')}", ""]
    missing = []
    for d in data.get("instruments", []):
        if d.get("ok"):
            parts.append(instrument_block(d))
            parts.append("")
        else:
            missing.append(d.get("display", "?"))
    if missing:
        parts.append(f"(unavailable this session: {', '.join(missing)})")
        parts.append("")
    parts.append("📈 Chart + levels are live on your dashboard:")
    return "\n".join(parts)


def send(token, chat_id, text):
    url = f"https://api.telegram.org/bot{token}/sendMessage"
    data = urllib.parse.urlencode({
        "chat_id": chat_id,
        "text": text,
        "disable_web_page_preview": "true",
    })
    # SSL bypass — mirrors live_signal_monitor_v3 (required on this VPS:
    # Windows Python 3.14, self-signed certificate in the chain)
    ctx = ssl.create_default_context()
    ctx.check_hostname = False
    ctx.verify_mode = ssl.CERT_NONE
    req = urllib.request.Request(url, data=data.encode(), method="POST")
    with urllib.request.urlopen(req, timeout=15, context=ctx):
        return True


def main():
    args = sys.argv[1:]
    force = "--force" in args
    only_to = None
    if "--to" in args:
        i = args.index("--to")
        if i + 1 < len(args):
            only_to = str(args[i + 1]).strip()
        else:
            sys.exit("--to needs a CHAT_ID")

    data = load_json(FORECAST_JSON, None)
    if not data or not data.get("instruments"):
        sys.exit("no forecast_data.json — run build_forecast.py first")
    sd = data.get("session_date", "")

    if only_to is None and not force:
        state = load_json(STATE_FILE, {})
        if state.get("session_date") == sd:
            print(f"already notified for session {sd} — nothing to do")
            return

    token = bot_token()
    if not token:
        sys.exit("no Telegram bot token found (env TELEGRAM_TOKEN or "
                 "live_signal_monitor_v3.py)")

    users = load_json(TOKENS_FILE, {}).get("tokens", [])
    users = [u for u in users if u.get("chat_id") and u.get("token")]
    if only_to is not None:
        users = [u for u in users if str(u.get("chat_id")) == only_to]
        if not users:
            sys.exit(f"chat_id {only_to} not found in dashboard_tokens.json")
    if not users:
        print("no dashboard users with tokens yet — nothing sent")
        return

    base = compose(data)
    sent = 0
    failed = 0
    for u in users:
        link = f"{FORECAST_PAGE_URL}?key={u['token']}"
        try:
            send(token, u["chat_id"], base + "\n" + link)
            sent += 1
            print(f"  sent -> {u.get('name', u['chat_id'])}")
        except Exception as e:
            failed += 1
            print(f"  FAIL -> {u.get('name', u['chat_id'])}: {e}")
        time.sleep(0.4)

    print(f"done: {sent} sent, {failed} failed (session {sd})")
    if only_to is None and sent > 0:
        tmp = STATE_FILE.with_name(STATE_FILE.name + ".tmp")
        with open(tmp, "w", encoding="utf-8") as f:
            json.dump({"session_date": sd, "sent": sent,
                       "at": datetime.now(timezone.utc).isoformat()}, f)
        import os
        os.replace(tmp, STATE_FILE)


if __name__ == "__main__":
    main()
