#!/usr/bin/env python3
import json, os, signal, subprocess, time, urllib.request
from pathlib import Path

BASE = os.environ.get('GITEA_API_BASE', 'https://forge.alexanderwhitestone.com/api/v1')
TIMMY_TOKEN = (Path.home() / '.config' / 'gitea' / 'timmy-token').read_text().strip()
LOG = Path('/tmp/codeclaw-qwen-heartbeat.log')
LOCK = Path('/tmp/codeclaw-qwen-heartbeat.lock')
ACTIVE = Path('/tmp/codeclaw-qwen-active.json')
REPOS = [
    'Timmy_Foundation/timmy-home',
    'Timmy_Foundation/timmy-config',
    'Timmy_Foundation/the-nexus',
    'Timmy_Foundation/hermes-agent',
]

headers = {'Authorization': f'token {TIMMY_TOKEN}', 'Accept': 'application/json', 'Content-Type': 'application/json'}

def log(msg):
    line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
    print(line)
    with LOG.open('a') as f:
        f.write(line + '\n')

def api(method, path, data=None):
    req = urllib.request.Request(f"{BASE}{path}", headers=headers, method=method)
    if data is not None:
        req.data = json.dumps(data).encode()
    with urllib.request.urlopen(req, timeout=30) as resp:
        raw = resp.read().decode()
        return json.loads(raw) if raw else {}


def process_alive(pid):
    try:
        os.kill(int(pid), 0)
    except (OSError, ProcessLookupError, ValueError, TypeError):
        return False
    return True

if LOCK.exists() and time.time() - LOCK.stat().st_mtime < 840:
    log('SKIP: lock active')
    raise SystemExit(0)
LOCK.write_text(str(os.getpid()))
try:
    if ACTIVE.exists():
        try:
            active = json.loads(ACTIVE.read_text())
            pid = active.get('pid')
            if pid and process_alive(pid):
                log(f'SKIP: worker still active for issue #{active.get("issue")}')
                raise SystemExit(0)
            ACTIVE.unlink(missing_ok=True)
        except Exception:
            pass

    picked = None
    for repo in REPOS:
        owner, name = repo.split('/')
        # candidate issues: assignee claw-code OR assigned-claw-code label
        issues = api('GET', f'/repos/{owner}/{name}/issues?state=open&type=issues&limit=50&sort=created')
        for i in reversed(issues):
            assignees = [a['login'].lower() for a in (i.get('assignees') or [])]
            labels = [l['name'] for l in i.get('labels', [])]
            if 'claw-code' not in assignees and 'assigned-claw-code' not in labels:
                continue
            if 'claw-code-done' in labels:
                continue
            if 'claw-code-in-progress' in labels:
                continue
            picked = (repo, i['number'], i['title'])
            break
        if picked:
            break

    if not picked:
        log('Heartbeat: no pending tasks')
        raise SystemExit(0)

    repo, issue_num, title = picked
    owner, name = repo.split('/')
    log(f'FOUND: {repo} #{issue_num} — {title}')

    # add in-progress label if available
    labels = api('GET', f'/repos/{owner}/{name}/labels?limit=100')
    progress_id = next((l['id'] for l in labels if l['name'] == 'claw-code-in-progress'), None)
    if progress_id:
        try:
            api('POST', f'/repos/{owner}/{name}/issues/{issue_num}/labels', {'labels': [progress_id]})
        except Exception:
            pass

    api('POST', f'/repos/{owner}/{name}/issues/{issue_num}/comments', {
        'body': f'🟠 Code Claw (OpenRouter qwen/qwen3.6-plus:free) picking up this issue via 15-minute heartbeat.\n\nTimestamp: {time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())}'
    })

    worker_log = Path(f'/tmp/codeclaw-qwen-worker-{issue_num}.log')
    fh = worker_log.open('a')
    cmd = ['python3', str(Path.home() / '.timmy' / 'uniwizard' / 'codeclaw_qwen_worker.py'), repo, str(issue_num), title]
    p = subprocess.Popen(cmd, stdout=fh, stderr=fh)
    ACTIVE.write_text(json.dumps({'pid': p.pid, 'issue': issue_num, 'repo': repo}))
    log(f'DISPATCHED: {repo} #{issue_num} (background PID {p.pid})')
finally:
    try:
        LOCK.unlink()
    except Exception:
        pass
