#!/usr/bin/env python3
import datetime as dt
import json
import os
import re
import html
import subprocess
import sys
from pathlib import Path
from typing import Optional
from urllib.request import Request, urlopen

BASE = Path('/home/isthekid/.openclaw/workspace/monitoring')
STATE_PATH = BASE / 'state' / 'openclaw-update-state.json'
LOG_PATH = BASE / 'logs' / 'openclaw-update-monitor.log'
RECIPIENT = 'adner.cruz@bertelsmann.com'
TIMEZONE = 'America/New_York'
GROK_VPS_HOST = 'ubuntu@150.136.218.171'
GROK_VPS_KEY = str(Path.home() / '.ssh' / 'oracle_vps')
OPENCLAW_BIN = str(Path.home() / '.npm-global' / 'bin' / 'openclaw')
GOG_ENV_FILE = Path.home() / '.openclaw' / 'secrets' / 'gog.env'


def load_env_file(path: Path) -> None:
    if not path.exists():
        return
    for line in path.read_text().splitlines():
        line = line.strip()
        if not line or line.startswith('#') or '=' not in line:
            continue
        key, value = line.split('=', 1)
        os.environ.setdefault(key.strip(), value.strip())


def run(cmd: list[str], timeout: int = 60) -> str:
    p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, timeout=timeout)
    if p.returncode != 0:
        raise RuntimeError(f"Command failed: {' '.join(cmd)}\nSTDERR:\n{p.stderr}\nSTDOUT:\n{p.stdout}")
    return p.stdout.strip()


def now_iso() -> str:
    return dt.datetime.now(dt.timezone.utc).isoformat()


def load_state() -> dict:
    if STATE_PATH.exists():
        return json.loads(STATE_PATH.read_text())
    return {
        'last_checked_at': None,
        'current_version_last_seen': None,
        'latest_version_last_seen': None,
        'last_alerted_version': None,
        'last_alerted_at': None,
        'source_validation': 'unknown',
        'last_summary_source': None,
        'last_summary_text': None,
    }


def save_state(state: dict) -> None:
    STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
    STATE_PATH.write_text(json.dumps(state, indent=2) + '\n')


def log(msg: str) -> None:
    LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
    line = f"[{now_iso()}] {msg}\n"
    with LOG_PATH.open('a') as f:
        f.write(line)


def parse_status_version() -> str:
    out = run([OPENCLAW_BIN, 'status', '--json'])
    start = out.find('{')
    end = out.rfind('}')
    if start == -1 or end == -1 or end <= start:
        raise RuntimeError('Could not locate JSON object in `openclaw status --json` output')
    data = json.loads(out[start:end + 1])
    return data.get('runtimeVersion', '').strip()


def parse_latest_from_update_status() -> str | None:
    out = run([OPENCLAW_BIN, 'update', 'status'])
    # Try to match: "npm latest 2026.3.12"
    m = re.search(r'npm\s+latest\s+([0-9]+(?:\.[0-9]+){1,3})', out)
    if m:
        return m.group(1)
    return None


def parse_latest_from_npm() -> str:
    out = run(['npm', 'view', 'openclaw', 'version'])
    return out.strip()


def compare_versions(a: str, b: str) -> int:
    # returns -1 if a<b, 0 if ==, 1 if a>b ; supports dotted numerics
    def parts(v: str):
        return [int(x) for x in re.findall(r'\d+', v)]
    pa, pb = parts(a), parts(b)
    n = max(len(pa), len(pb))
    pa += [0] * (n - len(pa))
    pb += [0] * (n - len(pb))
    return (pa > pb) - (pa < pb)


def fetch_release_notes(version: str) -> dict:
    # Primary: GitHub latest release endpoint
    url = 'https://api.github.com/repos/openclaw/openclaw/releases/latest'
    req = Request(url, headers={'Accept': 'application/vnd.github+json', 'User-Agent': 'openclaw-update-monitor'})
    try:
        with urlopen(req, timeout=20) as resp:
            data = json.loads(resp.read().decode('utf-8'))
        tag = data.get('tag_name', '')
        body = data.get('body', '') or ''
        html = data.get('html_url', '')
        if version in tag or tag.endswith(version) or compare_versions(version, re.sub(r'^[^0-9]*', '', tag)) == 0:
            return {
                'source': 'github',
                'title': data.get('name') or tag,
                'url': html,
                'notes': body[:4000]
            }
    except Exception:
        pass

    return {
        'source': 'fallback',
        'title': f'OpenClaw {version}',
        'url': f'https://github.com/openclaw/openclaw/releases',
        'notes': 'Release notes were not retrieved automatically. Please review release page.'
    }


def build_local_summary(current_version: str, new_version: str, source_validation: str, notes_url: str) -> str:
    return (
        f"• Change detected: OpenClaw {new_version} (current {current_version}).\n"
        f"• Validation path: {source_validation} (openclaw update status + npm).\n"
        "• Risk: delaying uptake keeps us on a release that may miss bug/security patches shipping in the current train.\n"
        "• Operational posture: schedule a controlled rollout (gateway restart + spot test) in the next nightly window unless an incident demands sooner.\n"
        "• Prep checklist: export config, confirm OAuth health, snapshot active cron + monitors.\n"
        f"• Review release notes: {notes_url}."
    )


def build_grok_summary(current_version: str, new_version: str, notes: str, notes_url: str) -> str:
    prompt = f"""You are an IT ops assistant. Write 8-12 concise lines for an executive update recommendation.
Context:
- Product: OpenClaw
- Current version: {current_version}
- New version: {new_version}
- Goal: help decide update now vs tonight vs defer
- Environment: direct Telegram use, gateway + cron usage, OAuth already restored.
Use this structure:
1) What changed (max 3 bullets)
2) What this means for us (max 4 bullets)
3) Recommendation with one of: Update now / Schedule tonight / Defer
Do not include raw URLs in the response.
Release notes excerpt:\n{notes[:2500]}"""

    remote_cmd = (
        "source ~/.secrets/api_keys.env >/dev/null 2>&1; "
        "python3 - <<'PY'\n"
        "import json, os, urllib.request\n"
        f"prompt = {json.dumps(prompt)}\n"
        "key = os.environ.get('XAI_API_KEY','').strip()\n"
        "if not key:\n"
        "  raise SystemExit('Missing XAI_API_KEY on VPS')\n"
        "payload = {\n"
        "  'model': 'grok-4-fast-non-reasoning',\n"
        "  'messages': [\n"
        "    {'role':'system','content':'Be precise and concise. No markdown headers.'},\n"
        "    {'role':'user','content':prompt}\n"
        "  ],\n"
        "  'temperature': 0.2\n"
        "}\n"
        "req = urllib.request.Request('https://api.x.ai/v1/chat/completions', data=json.dumps(payload).encode('utf-8'), headers={'Authorization': f'Bearer {key}', 'Content-Type':'application/json', 'Accept':'application/json', 'User-Agent':'xAI-Client/1.0 (OpenClaw Update Monitor)'})\n"
        "with urllib.request.urlopen(req, timeout=60) as r:\n"
        "  data = json.loads(r.read().decode('utf-8'))\n"
        "print(data['choices'][0]['message']['content'].strip())\n"
        "PY"
    )
    out = run(['ssh', '-i', GROK_VPS_KEY, GROK_VPS_HOST, remote_cmd], timeout=120)
    return out.strip()


def send_email(subject: str, body_html: str) -> None:
    run([
        'gog', 'gmail', 'send',
        '--account', 'srvdeskops@gmail.com',
        '--to', RECIPIENT,
        '--subject', subject,
        '--body-html', body_html,
    ], timeout=60)


def main() -> int:
    try:
        load_env_file(GOG_ENV_FILE)
        state = load_state()
        current_version = parse_status_version()
        latest_primary = parse_latest_from_update_status()
        latest_secondary = parse_latest_from_npm()

        latest = latest_primary or latest_secondary
        source_validation = 'both' if latest_primary and latest_primary == latest_secondary else ('secondary-only' if not latest_primary else 'mismatch')

        state['last_checked_at'] = now_iso()
        state['current_version_last_seen'] = current_version
        state['latest_version_last_seen'] = latest
        state['source_validation'] = source_validation

        if not latest:
            log('No latest version detected; no action.')
            save_state(state)
            return 0

        has_new = compare_versions(latest, current_version) > 0
        already_alerted = (state.get('last_alerted_version') == latest)

        if (not has_new) or already_alerted or source_validation == 'mismatch':
            reason = 'no-change'
            if source_validation == 'mismatch':
                reason = f'mismatch primary={latest_primary} secondary={latest_secondary}'
            elif already_alerted:
                reason = f'already-alerted={latest}'
            log(f'No email sent ({reason}). current={current_version} latest={latest}')
            save_state(state)
            return 0

        rn = fetch_release_notes(latest)
        try:
            summary = build_grok_summary(current_version, latest, rn['notes'], rn['url'])
            summary_source = 'grok'
        except Exception as ge:
            log(f'Grok summary failed; using fallback. error={ge}')
            summary_source = 'fallback'
            summary = build_local_summary(current_version, latest, source_validation, rn['url'])

        state['last_summary_source'] = summary_source
        state['last_summary_text'] = summary

        subject = f"[OpenClaw Update] {latest} available (current: {current_version})"
        summary_html = html.escape(summary).replace('\n', '<br>')
        body_html = (
            f"<p>Hi Adner,</p>"
            f"<p>Current version: {html.escape(current_version)}<br>"
            f"New available version: {html.escape(latest)}<br>"
            f"Detected at: {html.escape(state['last_checked_at'])}<br>"
            f"Validation: {html.escape(source_validation)} (openclaw update status + npm)<br>"
            f"Recommendation source: {html.escape(summary_source)}<br>"
            f"Changelog: <a href=\"{html.escape(rn['url'], quote=True)}\">OpenClaw Release Notes</a></p>"
            f"<p><strong>What changed and what this means for us:</strong><br>{summary_html}</p>"
            f"<p>Automated change-only alert (daily 6:00 AM ET)</p>"
        )
        send_email(subject, body_html)

        state['last_alerted_version'] = latest
        state['last_alerted_at'] = now_iso()
        save_state(state)
        log(f'Email sent for new version {latest} (current={current_version}).')
        return 0

    except Exception as e:
        log(f'ERROR: {e}')
        return 1


if __name__ == '__main__':
    sys.exit(main())
