#!/usr/bin/env python3
"""Merge one TradingView MCP data_get_ohlcv JSON result into stored OHLCV files.

This keeps raw MCP output out of LLM context. The agent can save tool output to a
file (or pipe stdin) and call this script. The script filters, deduplicates,
updates collector_state, validates JSON, and prints a tiny summary.
"""
from __future__ import annotations

import argparse
import json
import sys
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'
TOLERANCE = {'M5': 300, 'M15': 900, 'M30': 1800, 'H1': 3600, 'H4': 14400, 'D': 86400, 'W': 604800}


def load_tool_result(text: str) -> dict:
    data = json.loads(text)
    # Hermes MCP tool output sometimes wraps JSON as {"result": "{...}"}
    if isinstance(data, dict) and isinstance(data.get('result'), str):
        data = json.loads(data['result'])
    if isinstance(data, dict) and 'content' in data and isinstance(data['content'], list):
        # Best-effort MCP content wrapper
        for item in data['content']:
            if isinstance(item, dict) and item.get('text'):
                try:
                    return json.loads(item['text'])
                except Exception:
                    pass
    return data


def update_progress(state: dict) -> None:
    chunks = state.get('chunks', [])
    total = len(chunks)
    done = sum(1 for c in chunks if c.get('status') == 'done')
    failed = sum(1 for c in chunks if c.get('status') == 'failed')
    pending = sum(1 for c in chunks if c.get('status') == 'pending')
    state['progress'] = {'done': done, 'failed': failed, 'pending': pending, 'total': total}
    state['status'] = 'complete' if total and done == total else 'in_progress'
    state['updated_at'] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00','Z')


def main() -> int:
    ap = argparse.ArgumentParser()
    ap.add_argument('--chunk-id', required=True)
    ap.add_argument('--input', help='File containing raw MCP JSON result; defaults stdin')
    args = ap.parse_args()

    state = json.loads(STATE.read_text())
    chunks = state.get('chunks', [])
    idx = next((i for i, c in enumerate(chunks) if c.get('id') == args.chunk_id), None)
    if idx is None:
        raise SystemExit(f'chunk not found: {args.chunk_id}')
    chunk = chunks[idx]
    suffix = chunk['suffix']
    tolerance = TOLERANCE.get(suffix, 0)
    output_rel = state.get('files', {}).get(suffix) or f'hybrid/data/ohlcv_{suffix}.json'
    output = ROOT / output_rel

    raw = Path(args.input).read_text() if args.input else sys.stdin.read()
    tv = load_tool_result(raw)
    bars = tv.get('bars') or []
    start = int(chunk['from']) - tolerance
    end = int(chunk['to']) + tolerance
    valid = [b for b in bars if isinstance(b, dict) and isinstance(b.get('time'), (int, float)) and start <= int(b['time']) <= end]

    chunk['attempts'] = int(chunk.get('attempts') or 0) + 1
    chunk['bars_collected'] = len(valid)
    if valid:
        chunk['status'] = 'done'
        chunk['last_error'] = None
    else:
        chunk['status'] = 'failed'
        first = bars[0].get('time') if bars and isinstance(bars[0], dict) else None
        chunk['last_error'] = f'No in-range bars. returned={len(bars)} first_time={first} range={chunk.get("from")}-{chunk.get("to")}'

    output.parent.mkdir(parents=True, exist_ok=True)
    if output.exists():
        out = json.loads(output.read_text())
    else:
        out = {'metadata': {'symbol': state.get('symbol','OANDA:XAUUSD'), 'timeframe': suffix, 'source': 'tradingview_mcp_visible_range_chunks'}, 'bars': []}
    existing = {int(b['time']) for b in out.get('bars', []) if isinstance(b, dict) and b.get('time') is not None}
    new = [b for b in valid if int(b['time']) not in existing]
    out.setdefault('bars', []).extend(new)
    out['bars'].sort(key=lambda b: int(b['time']))
    if out['bars']:
        out['metadata']['start'] = out['bars'][0]['time']
        out['metadata']['end'] = out['bars'][-1]['time']
    out['metadata']['bar_count'] = len(out['bars'])
    out['metadata']['last_updated'] = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace('+00:00','Z')

    chunks[idx] = chunk
    state['chunks'] = chunks
    update_progress(state)
    output.write_text(json.dumps(out, indent=2) + '\n')
    STATE.write_text(json.dumps(state, indent=2) + '\n')
    # Validate by parsing back
    json.loads(output.read_text())
    json.loads(STATE.read_text())
    next_chunk = next((c.get('id') for c in chunks if c.get('status') in ('pending','failed') and int(c.get('attempts') or 0) < 3), None)
    print(f"RESULT chunk_id={args.chunk_id} suffix={suffix} returned={len(bars)} kept={len(valid)} added={len(new)} total_bars={len(out['bars'])} progress={state['progress']['done']}/{state['progress']['total']} next={next_chunk}")
    return 0


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