#!/usr/bin/env python3
"""Continuous Hermes one-shot worker for XAUUSD hybrid M15/M5 OHLCV collection.

This wrapper does not call TradingView MCP directly. Instead it launches one Hermes
agent run per chunk. Each agent run processes exactly one pending chunk from
hybrid/collector_state.json, then exits. The wrapper immediately starts the next
chunk until all M15/M5 chunks are done or the process is stopped.
"""
from __future__ import annotations

import json
import os
import subprocess
import sys
import time
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path("/home/aryy/.hermes/profiles/finance/backtests/xauusd_2026")
STATE = ROOT / "hybrid/collector_state.json"
LOG_DIR = ROOT / "hybrid/reports"
LOG_DIR.mkdir(parents=True, exist_ok=True)
LOG_FILE = LOG_DIR / "continuous_collector.log"
LOCK = ROOT / "hybrid/collector_worker.lock"

PROMPT = r"""
You are Aryy Finance data collector. Process exactly ONE pending XAUUSD OHLCV chunk for the hybrid backtest, then stop.

Project directory: /home/aryy/.hermes/profiles/finance/backtests/xauusd_2026
Collector state: hybrid/collector_state.json
Scope: M15 and M5 only. Do not collect D/H4/M30. Do not run scanner. Do not write trades.

Process:
1. Read hybrid/collector_state.json.
2. Find the first chunk with status='pending', or status='failed' and attempts < 3, where suffix is M15 or M5.
3. If no such chunk exists, update collector_state.status='complete' if all chunks are done, then final response 'COMPLETE'.
4. Use TradingView MCP only:
   - chart_set_symbol OANDA:XAUUSD
   - chart_set_timeframe chunk.timeframe
   - chart_set_visible_range from=chunk.from to=chunk.to
   - data_get_ohlcv count=500 summary=false
5. Parse returned bars. Keep only bars with numeric time inside chunk.from..chunk.to plus one timeframe tolerance (M15=900 sec, M5=300 sec). Discard unrelated bars.
6. Merge kept bars into output file:
   - M15 -> hybrid/data/ohlcv_M15.json
   - M5 -> hybrid/data/ohlcv_M5.json
   Preserve existing bars, deduplicate by time, sort ascending.
   Schema: {"metadata":{"symbol":"XAUUSD","timeframe":"M15 or M5","source":"tradingview_mcp_visible_range_chunks","start":"2026-01-01","end":"2026-07-17","bar_count":N,"last_updated":"UTC ISO"},"bars":[{"time":int,"open":float,"high":float,"low":float,"close":float,"volume":number|null}]}
7. Update the chunk:
   - attempts += 1
   - bars_collected = number of kept bars
   - status='done' if kept bars > 0, else 'failed'
   - last_error = null or concise reason
8. Update collector_state.status='in_progress' and progress summary fields if useful.
9. Validate JSON for collector_state and changed output file using terminal python3 -m json.tool.
10. Final response must be one line: chunk id, suffix, range, kept bars, output total bars, next pending count.

Hard rules:
- Exactly ONE chunk per run.
- Do not use execute_code.
- Do not use screenshots/vision.
- Do not analyze strategy.
- Do not write data/journal.json, backtest-data, shadow-trades, candidates, or reviews.
- If MCP returns no in-range bars, mark that chunk failed and stop; do not loop.
""".strip()


def log(msg: str) -> None:
    line = f"{datetime.now(timezone.utc).isoformat()} {msg}"
    print(line, flush=True)
    with LOG_FILE.open("a", encoding="utf-8") as f:
        f.write(line + "\n")


def load_state() -> dict:
    return json.loads(STATE.read_text())


def counts(state: dict) -> dict[str, int]:
    out: dict[str, int] = {}
    for c in state.get("chunks", []):
        out[c.get("status", "unknown")] = out.get(c.get("status", "unknown"), 0) + 1
    return out


def all_done(state: dict) -> bool:
    return all(c.get("status") == "done" for c in state.get("chunks", []))


def main() -> int:
    os.chdir(ROOT)
    if LOCK.exists():
        log(f"LOCK_EXISTS {LOCK}; exiting to avoid duplicate worker")
        return 2
    LOCK.write_text(json.dumps({"started_at": datetime.now(timezone.utc).isoformat(), "pid": os.getpid()}, indent=2) + "\n")
    try:
        iteration = 0
        while True:
            state = load_state()
            cts = counts(state)
            log(f"STATE {cts}")
            if all_done(state):
                log("COMPLETE all chunks done")
                state["status"] = "complete"
                state["completed_at"] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
                STATE.write_text(json.dumps(state, indent=2) + "\n")
                return 0
            iteration += 1
            log(f"RUN_CHUNK iteration={iteration}")
            env = os.environ.copy()
            env["HERMES_PROFILE"] = "finance"
            cmd = [
                "hermes",
                "--provider", "custom:9router",
                "--model", "ag/gemini-pro-agent",
                "-z", PROMPT,
            ]
            proc = subprocess.run(cmd, cwd=str(ROOT), env=env, text=True, capture_output=True, timeout=900)
            log(f"HERMES_EXIT code={proc.returncode}")
            if proc.stdout.strip():
                log("STDOUT " + proc.stdout.strip().replace("\n", " | ")[-3000:])
            if proc.stderr.strip():
                log("STDERR " + proc.stderr.strip().replace("\n", " | ")[-3000:])
            if proc.returncode != 0:
                log("STOP nonzero hermes exit; leaving state for inspection")
                return proc.returncode
            # small delay so TradingView/CDP can breathe
            time.sleep(2)
    finally:
        try:
            LOCK.unlink(missing_ok=True)
        except Exception as exc:
            log(f"LOCK_REMOVE_ERROR {exc}")


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