#!/usr/bin/env python3
"""Wisdom Exposed local operator for individually approved tasks.

Nothing runs without a task approved in Wisdom Exposed AND the matching explicit flag.
"""
from __future__ import annotations

import argparse
import json
import os
from pathlib import Path
import re
import subprocess
import sys
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone
from typing import Any

DESTRUCTIVE = re.compile(r'rm\s+-rf|\bmkfs\b|\bdd\b[^\n]*of=/dev/|:\(\)\s*\{|\bshutdown\b|\breboot\b', re.I)


def api(server: str, token: str, payload: dict[str, Any]) -> dict[str, Any]:
    request = urllib.request.Request(server.rstrip('/') + '/api/operator/tasks', data=json.dumps(payload).encode(), headers={'Content-Type': 'application/json', 'X-Wisdom-Exposed-Token': token})
    try:
        with urllib.request.urlopen(request, timeout=30) as response:
            return json.loads(response.read().decode())
    except urllib.error.HTTPError as error:
        raise RuntimeError(f'HTTP {error.code}: {error.read().decode(errors="replace")[:800]}') from error
    except urllib.error.URLError as error:
        raise RuntimeError(f'Cannot reach Wisdom Exposed: {error.reason}') from error


def report(server: str, token: str, task: dict[str, Any], status: str, output: str) -> None:
    api(server, token, {'action': 'report', 'id': task.get('id'), 'status': status, 'output': output[-12000:], 'reportedAt': datetime.now(timezone.utc).isoformat()})


def execute(task: dict[str, Any], args: argparse.Namespace) -> str:
    command = str(task.get('command', ''))
    scope = task.get('scope', 'terminal')
    if DESTRUCTIVE.search(command):
        raise RuntimeError('Destructive pattern refused by the local operator')
    if scope == 'terminal':
        if not args.enable_terminal:
            raise RuntimeError('Terminal execution requires --enable-terminal')
        completed = subprocess.run(command, shell=True, text=True, capture_output=True, timeout=args.timeout)
        output = (completed.stdout + completed.stderr).strip()
        if completed.returncode:
            raise RuntimeError(f'Exit {completed.returncode}: {output}')
        return output or '(command completed with no output)'
    if scope == 'screen':
        if not args.enable_screenshots:
            raise RuntimeError('Screenshots require --enable-screenshots')
        destination = Path.home() / '.wisdom-exposed' / 'captures'
        destination.mkdir(parents=True, exist_ok=True)
        target = destination / f'capture-{int(time.time())}.png'
        if sys.platform == 'darwin':
            subprocess.run(['screencapture', '-x', str(target)], check=True, timeout=20)
        elif shutil_which('gnome-screenshot'):
            subprocess.run(['gnome-screenshot', '-f', str(target)], check=True, timeout=20)
        else:
            raise RuntimeError('No supported screenshot command found; install a platform screenshot tool')
        return str(target)
    if scope == 'keyboard':
        if not args.allow_keyboard:
            raise RuntimeError('Keyboard execution requires --enable-terminal and --allow-keyboard')
        if not args.enable_terminal:
            raise RuntimeError('Keyboard execution also requires --enable-terminal')
        try:
            from pynput.keyboard import Controller
        except ImportError as error:
            raise RuntimeError('Keyboard support requires: pip install pynput') from error
        text = task.get('text')
        if not isinstance(text, str) or len(text) > 2000:
            raise RuntimeError('Keyboard task needs a text field of at most 2000 characters')
        Controller().type(text)
        return f'Typed {len(text)} approved characters'
    raise RuntimeError(f'Unsupported or disabled scope: {scope}')


def shutil_which(command: str) -> str | None:
    from shutil import which
    return which(command)


def main() -> int:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('--server', default=os.getenv('WISDOM_EXPOSED_SERVER') or os.getenv('SIDECAR_SERVER') or 'http://localhost:3000')
    parser.add_argument('--token', default=os.getenv('WISDOM_EXPOSED_TOKEN') or os.getenv('SIDECAR_TOKEN') or '')
    parser.add_argument('--dry-run', action='store_true', help='Preview approved tasks without claiming or executing')
    parser.add_argument('--enable-screenshots', action='store_true')
    parser.add_argument('--enable-terminal', action='store_true')
    parser.add_argument('--allow-keyboard', action='store_true')
    parser.add_argument('--interval', type=int, default=0)
    parser.add_argument('--timeout', type=int, default=60)
    args = parser.parse_args()
    if not args.token:
        parser.error('set WISDOM_EXPOSED_TOKEN or pass --token')

    def cycle() -> None:
        result = api(args.server, args.token, {'action': 'preview' if args.dry_run else 'claim'})
        tasks = result.get('tasks', result.get('operations', []))
        print(f'{len(tasks)} approved tasks waiting')
        for task in tasks:
            print(f'[{task.get("scope", "terminal")}] {task.get("command", "")} — {task.get("rationale", "")}')
            if args.dry_run:
                continue
            try:
                output = execute(task, args)
                report(args.server, args.token, task, 'complete', output)
                print('COMPLETE:', output[:500])
            except Exception as error:
                report(args.server, args.token, task, 'failed', str(error))
                print('REFUSED/FAILED:', error, file=sys.stderr)

    while True:
        cycle()
        if args.interval <= 0:
            break
        time.sleep(max(10, args.interval))
    return 0


if __name__ == '__main__':
    try:
        raise SystemExit(main())
    except KeyboardInterrupt:
        raise SystemExit(130)
