#!/usr/bin/env python3
"""Import one or more TradingView 'Export chart data' CSV files into hybrid OHLCV JSON.

Usage examples:
  python3 hybrid/scripts/import_tv_exports.py --timeframe M15 hybrid/data/raw/*.csv
  python3 hybrid/scripts/import_tv_exports.py --timeframe M5 hybrid/data/raw/m5_*.csv

Output:
  hybrid/data/ohlcv_<TIMEFRAME>.json

The importer deduplicates by timestamp and sorts ascending. It accepts common
TradingView CSV column names such as time/time UTC/Date, open, high, low, close,
volume.
"""
from __future__ import annotations
import argparse, csv, json, re
from datetime import datetime, timezone
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
DATA = ROOT / "data"

TIME_ALIASES = ["time", "time utc", "date", "datetime", "timestamp"]
OPEN_ALIASES = ["open", "open price"]
HIGH_ALIASES = ["high", "high price"]
LOW_ALIASES = ["low", "low price"]
CLOSE_ALIASES = ["close", "close price", "last"]
VOL_ALIASES = ["volume", "vol"]


def norm_key(s: str) -> str:
    return re.sub(r"\s+", " ", s.strip().lower().replace("_", " "))


def pick(headers: list[str], aliases: list[str], required=True) -> str | None:
    mapping = {norm_key(h): h for h in headers}
    for a in aliases:
        if a in mapping:
            return mapping[a]
    if required:
        raise SystemExit(f"Missing required column. Need one of {aliases}; got {headers}")
    return None


def parse_time(v: str) -> str:
    text = str(v).strip()
    # Unix seconds or milliseconds
    if re.fullmatch(r"\d+(\.\d+)?", text):
        x = float(text)
        if x > 10_000_000_000:
            x /= 1000
        return datetime.fromtimestamp(x, tz=timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
    # TradingView often exports like 2026-01-02T13:45:00Z or 2026-01-02 13:45
    text = text.replace(" UTC", "").replace("Z", "+00:00")
    try:
        dt = datetime.fromisoformat(text)
    except ValueError:
        for fmt in ["%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M", "%d/%m/%Y %H:%M:%S", "%d/%m/%Y %H:%M", "%m/%d/%Y %H:%M:%S", "%m/%d/%Y %H:%M"]:
            try:
                dt = datetime.strptime(text, fmt)
                break
            except ValueError:
                pass
        else:
            raise
    if dt.tzinfo is None:
        dt = dt.replace(tzinfo=timezone.utc)
    return dt.astimezone(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")


def to_float(v):
    if v is None or v == "":
        return None
    return float(str(v).replace(",", ""))


def read_csv(path: Path):
    with path.open(newline="", encoding="utf-8-sig") as f:
        reader = csv.DictReader(f)
        headers = reader.fieldnames or []
        time_col = pick(headers, TIME_ALIASES)
        open_col = pick(headers, OPEN_ALIASES)
        high_col = pick(headers, HIGH_ALIASES)
        low_col = pick(headers, LOW_ALIASES)
        close_col = pick(headers, CLOSE_ALIASES)
        vol_col = pick(headers, VOL_ALIASES, required=False)
        for row in reader:
            yield {
                "time": parse_time(row[time_col]),
                "open": to_float(row[open_col]),
                "high": to_float(row[high_col]),
                "low": to_float(row[low_col]),
                "close": to_float(row[close_col]),
                "volume": to_float(row[vol_col]) if vol_col else None,
                "source_file": path.name,
            }


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("files", nargs="+", help="TradingView CSV exports")
    ap.add_argument("--timeframe", required=True, help="M15, M5, M30, H4, D")
    ap.add_argument("--start", default="2026-01-01")
    ap.add_argument("--end", default="2026-07-17")
    args = ap.parse_args()

    start = datetime.fromisoformat(args.start).replace(tzinfo=timezone.utc)
    end = datetime.fromisoformat(args.end).replace(tzinfo=timezone.utc)
    rows = {}
    for pat in args.files:
        for fp in sorted(Path().glob(pat) if any(ch in pat for ch in "*?[") else [Path(pat)]):
            for bar in read_csv(fp):
                dt = datetime.fromisoformat(bar["time"].replace("Z", "+00:00"))
                if start <= dt <= end:
                    rows[bar["time"]] = bar
    bars = [rows[k] for k in sorted(rows)]
    out = DATA / f"ohlcv_{args.timeframe.upper()}.json"
    obj = {
        "metadata": {
            "symbol": "XAUUSD",
            "timeframe": args.timeframe.upper(),
            "source": "tradingview_export_chart_data_visible_range_chunks",
            "start": args.start,
            "end": args.end,
            "bar_count": len(bars),
        },
        "bars": bars,
    }
    out.write_text(json.dumps(obj, indent=2) + "\n")
    print(f"wrote {len(bars)} bars -> {out}")

if __name__ == "__main__":
    main()
