#!/usr/bin/env python3
"""Pull approved Pi requests from Nextcloud into this repo.

This is a controlled, operator-run bridge between the Nextcloud Talk bot and the
coding-agent workspace. It does not store Nextcloud secrets locally. Instead it
SSHes to the Nextcloud VM and uses the already-deployed bridge config/env there
to read the request queue from Nextcloud Files.
"""

from __future__ import annotations

import argparse
import hashlib
import json
import re
import subprocess
from datetime import datetime
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
DEFAULT_STATE = ROOT / ".tokens" / "nextcloud-pi-request-queue-state.json"
DEFAULT_INBOX = ROOT / "inbox" / "capture.md"
DEFAULT_SSH_KEY = ROOT / ".ssh" / "piagent_homelab"
DEFAULT_KNOWN_HOSTS = ROOT / ".ssh" / "known_hosts"
DEFAULT_REMOTE = "piagent@192.168.0.110"

REMOTE_FETCH_SCRIPT = r'''
import base64
import json
import os
import shlex
import sys
import urllib.error
import urllib.parse
import urllib.request
from pathlib import Path

cfg = json.loads(Path('/etc/nextcloud-talk-assistant/config.json').read_text())
env = {}
for line in Path('/etc/nextcloud-talk-assistant/env').read_text().splitlines():
    line = line.strip()
    if not line or line.startswith('#') or '=' not in line:
        continue
    key, value = line.split('=', 1)
    env[key] = shlex.split(value)[0] if value else ''
password = env.get('NEXTCLOUD_APP_PASSWORD') or os.environ.get('NEXTCLOUD_APP_PASSWORD')
if not password:
    raise SystemExit('NEXTCLOUD_APP_PASSWORD not found on remote VM')
base_url = cfg['nextcloud_base_url'].rstrip('/')
username = cfg['username']
file_path = cfg.get('pi_request_file_path') or 'Assistant/pi-requests.md'
quoted = '/'.join(urllib.parse.quote(part) for part in file_path.strip('/').split('/'))
url = f"{base_url}/remote.php/dav/files/{urllib.parse.quote(username)}/{quoted}"
token = base64.b64encode(f"{username}:{password}".encode()).decode()
req = urllib.request.Request(url, headers={'Authorization': f'Basic {token}', 'User-Agent': 'pi-request-queue-fetch'})
try:
    with urllib.request.urlopen(req, timeout=30) as resp:
        sys.stdout.write(resp.read().decode('utf-8', 'replace'))
except urllib.error.HTTPError as exc:
    if exc.code == 404:
        sys.exit(0)
    detail = exc.read().decode('utf-8', 'replace')[:300]
    raise SystemExit(f'WebDAV read failed HTTP {exc.code}: {detail}')
'''

REQUEST_RE = re.compile(r"^-\s+(?P<stamp>\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2})\s+—\s+assistant_request:\s+(?P<text>.+?)\s*$")


def run_ssh_fetch(remote: str, ssh_key: Path, known_hosts: Path) -> str:
    cmd = [
        "ssh",
        "-o", "BatchMode=yes",
        "-o", "ConnectTimeout=10",
        "-o", "StrictHostKeyChecking=accept-new",
        "-o", f"UserKnownHostsFile={known_hosts}",
        "-i", str(ssh_key),
        remote,
        "sudo", "-n", "python3", "-",
    ]
    result = subprocess.run(cmd, cwd=ROOT, input=REMOTE_FETCH_SCRIPT, text=True, capture_output=True, check=False, timeout=60)
    if result.returncode != 0:
        raise SystemExit(result.stderr.strip() or result.stdout.strip() or f"ssh fetch failed with code {result.returncode}")
    return result.stdout


def parse_requests(markdown: str) -> list[dict]:
    requests = []
    for line in markdown.splitlines():
        match = REQUEST_RE.match(line.strip())
        if not match:
            continue
        raw = line.strip()
        request_id = hashlib.sha256(raw.encode('utf-8')).hexdigest()[:16]
        requests.append({
            "id": request_id,
            "stamp": match.group('stamp'),
            "text": match.group('text').strip(),
            "raw": raw,
        })
    return requests


def load_state(path: Path) -> dict:
    if not path.exists():
        return {"imported_ids": []}
    return json.loads(path.read_text())


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


def append_to_inbox(path: Path, requests: list[dict]) -> None:
    path.parent.mkdir(parents=True, exist_ok=True)
    now = datetime.now().strftime('%Y-%m-%d %H:%M')
    lines = ["", f"## Imported Nextcloud Pi requests — {now}", ""]
    for req in requests:
        lines.append(f"- [ ] {req['text']} _(Talk request {req['stamp']}, id `{req['id']}`)_")
    lines.append("")
    with path.open("a", encoding="utf-8") as f:
        f.write("\n".join(lines))


def main() -> int:
    parser = argparse.ArgumentParser(description="Fetch/import approved Pi requests from Nextcloud Talk")
    parser.add_argument("--remote", default=DEFAULT_REMOTE)
    parser.add_argument("--ssh-key", type=Path, default=DEFAULT_SSH_KEY)
    parser.add_argument("--known-hosts", type=Path, default=DEFAULT_KNOWN_HOSTS)
    parser.add_argument("--state", type=Path, default=DEFAULT_STATE)
    parser.add_argument("--inbox", type=Path, default=DEFAULT_INBOX)
    parser.add_argument("--import", dest="do_import", action="store_true", help="Append new requests to inbox/capture.md and mark imported")
    args = parser.parse_args()

    markdown = run_ssh_fetch(args.remote, args.ssh_key, args.known_hosts)
    requests = parse_requests(markdown)
    state = load_state(args.state)
    imported = set(state.get("imported_ids", []))
    new_requests = [req for req in requests if req["id"] not in imported]

    if not new_requests:
        print("No new Pi requests.")
        return 0

    for req in new_requests:
        print(f"{req['id']}  {req['stamp']}  {req['text']}")

    if args.do_import:
        append_to_inbox(args.inbox, new_requests)
        imported.update(req["id"] for req in new_requests)
        state["imported_ids"] = sorted(imported)
        save_state(args.state, state)
        print(f"Imported {len(new_requests)} request(s) into {args.inbox.relative_to(ROOT)}")
    else:
        print("Run again with --import to append these to inbox/capture.md")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
