"""event_capture.py — Captures new trade events from trading_ledger.ndjson.

Uses a cursor file to track the last synced timestamp, enabling
incremental sync: only events newer than the cursor are emitted.

Usage:
    events = get_new_trade_events(tenant_id="default")
"""

from __future__ import annotations

import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

import config_manager

APP_DIR = config_manager.get_app_dir()
DATA_DIR = config_manager.get_data_dir()
LOGS_DIR = config_manager.get_logs_dir()

# ── Cursor file stored under data/uploads/ ─────────────────────────
UPLOAD_DIR = DATA_DIR / "uploads"
CURSOR_FILE = UPLOAD_DIR / ".cursor.json"
LEDGER_FILE = LOGS_DIR / "trading_ledger.ndjson"

# ── Schema: fields expected per trade event ─────────────────────────
REQUIRED_FIELDS = [
    "ts",
    "side",
    "symbol",
    "volume",
    "entry",
    "exit",
    "pnl",
]
OPTIONAL_FIELDS = [
    "ticket",
    "regime",
    "confidence",
    "execution_latency_ms",
    "order_type",
    "action",
    "status",
    "decision_id",
    "intent_id",
    "signal_id",
    "close_reason",
    "exit_ts",
]


# ── Cursor management ────────────────────────────────────────────────

def _load_cursor(cursor_path: Path | None = None) -> dict[str, Any]:
    path = cursor_path or CURSOR_FILE
    if not path.exists():
        return {"last_synced_ts": None, "last_synced_line": 0}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        return {"last_synced_ts": None, "last_synced_line": 0}


def _save_cursor(cursor: dict[str, Any]) -> None:
    UPLOAD_DIR.mkdir(parents=True, exist_ok=True)
    tmp = CURSOR_FILE.with_name(f".cursor.{os.getpid()}.tmp")
    tmp.write_text(json.dumps(cursor, ensure_ascii=False), encoding="utf-8")
    tmp.replace(CURSOR_FILE)


# ── Ledger parsing ──────────────────────────────────────────────────

def _parse_ts(value: Any) -> datetime | None:
    """Parse a timestamp from various formats."""
    if value is None:
        return None
    if isinstance(value, datetime):
        return value
    s = str(value).strip()
    if not s:
        return None
    try:
        import math as _m
        # ISO with timezone
        if s.endswith("Z"):
            return datetime.fromisoformat(s.replace("Z", "+00:00"))
        if "+" in s or s.count("-") > 2:
            return datetime.fromisoformat(s)
        # Unix timestamp (seconds or milliseconds)
        if s.isdigit():
            n = int(s)
            if n > 1e12:  # milliseconds
                return datetime.fromtimestamp(n / 1000, tz=timezone.utc)
            return datetime.fromtimestamp(n, tz=timezone.utc)
        return datetime.fromisoformat(s)
    except Exception:
        return None


def _parse_ledger_line(line: str) -> dict[str, Any] | None:
    try:
        event = json.loads(line)
        if not isinstance(event, dict):
            return None
        # Only upload realized trade outcomes; OPEN/ACKED pending entries are not trade results.
        action = str(event.get("action", "")).upper()
        if action != "CLOSE":
            return None
        status = str(event.get("status", "FILLED") or "FILLED").upper()
        if status not in {"", "FILLED", "RECONCILED"}:
            return None
        return event
    except Exception:
        return None


def _classify_ledger_event(event: dict[str, Any] | None) -> str:
    if not isinstance(event, dict):
        return "invalid"
    action = str(event.get("action", "") or "").upper()
    status = str(event.get("status", "FILLED") or "FILLED").upper()
    if action == "CLOSE" and status in {"", "FILLED", "RECONCILED"}:
        return "realized_close"
    if action == "CLOSE":
        return "close_nonfinal"
    if action == "OPEN":
        return "open_event"
    return "other"


# ── Public API ──────────────────────────────────────────────────────

def get_new_trade_events(
    ledger_file: Path | None = None,
    cursor_file: Path | None = None,
    batch_size: int = 500,
) -> tuple[list[dict[str, Any]], dict[str, Any]]:
    """Capture all trade events since the last synced cursor.

    Args:
        ledger_file: Path to trading_ledger.ndjson. Defaults to LOGS_DIR/trading_ledger.ndjson.
        cursor_file: Path to cursor file. Defaults to UPLOAD_DIR/.cursor.json.
        batch_size: Maximum events to return per call (cap for upload batching).

    Returns:
        (events, cursor_update) where events is a list of trade event dicts
        and cursor_update contains the new cursor state to be saved after a successful upload.
    """
    ledger = ledger_file or LEDGER_FILE
    cursor_path = cursor_file or CURSOR_FILE
    cursor = _load_cursor(cursor_path)

    if not ledger.exists():
        return [], {"last_synced_ts": None, "last_synced_line": 0, "error": "ledger_not_found"}

    try:
        lines = ledger.read_text(encoding="utf-8").strip().split("\n")
    except Exception as e:
        return [], {"last_synced_ts": None, "last_synced_line": 0, "error": str(e)}

    last_synced_line = int(cursor.get("last_synced_line", 0) or 0)
    last_synced_ts = cursor.get("last_synced_ts")

    events: list[dict[str, Any]] = []
    new_last_ts = last_synced_ts
    new_last_line = last_synced_line

    for i, line in enumerate(lines):
        if i < last_synced_line:
            continue
        if len(events) >= batch_size:
            # Stop at batch_size — next call will pick up from here
            break

        event = _parse_ledger_line(line)
        if not event:
            continue

        ts_val = event.get("ts") or event.get("timestamp")
        event_ts = _parse_ts(ts_val)

        # Skip if older than cursor ts
        if event_ts and last_synced_ts:
            cursor_dt = _parse_ts(last_synced_ts)
            if cursor_dt and event_ts <= cursor_dt:
                continue

        events.append(event)

        # Update cursor trackers
        if ts_val:
            new_last_ts = ts_val
        new_last_line = i + 1

    cursor_update = {
        "last_synced_ts": new_last_ts,
        "last_synced_line": new_last_line,
        "events_captured": len(events),
        "cursor_updated_at": datetime.now(timezone.utc).isoformat(),
    }

    return events, cursor_update


def get_all_trade_events(
    ledger_file: Path | None = None,
    since_ts: str | None = None,
    until_ts: str | None = None,
) -> list[dict[str, Any]]:
    """Read all trade events in a time range (for bulk export)."""
    ledger = ledger_file or LEDGER_FILE
    if not ledger.exists():
        return []

    since_dt = _parse_ts(since_ts) if since_ts else None
    until_dt = _parse_ts(until_ts) if until_ts else None

    events: list[dict[str, Any]] = []
    for line in ledger.read_text(encoding="utf-8").strip().split("\n"):
        if not line.strip():
            continue
        event = _parse_ledger_line(line)
        if not event:
            continue
        ts_val = event.get("ts") or event.get("timestamp")
        event_dt = _parse_ts(ts_val)
        if since_dt and event_dt and event_dt < since_dt:
            continue
        if until_dt and event_dt and event_dt > until_dt:
            continue
        events.append(event)
    return events


def get_upload_status() -> dict[str, Any]:
    """Return current upload state (last sync time, line, lag)."""
    cursor = _load_cursor()
    last_ts = cursor.get("last_synced_ts")

    lag_sec: float | int = 0
    if last_ts:
        dt = _parse_ts(last_ts)
        if dt:
            lag_sec = (datetime.now(timezone.utc) - dt).total_seconds()

    ledger_exists = LEDGER_FILE.exists()
    ledger_lines = 0
    realized_close_lines = 0
    open_event_lines = 0
    nonfinal_close_lines = 0
    filtered_event_lines = 0
    if ledger_exists:
        try:
            raw_lines = [line for line in LEDGER_FILE.read_text(encoding="utf-8").splitlines() if line.strip()]
            ledger_lines = len(raw_lines)
            for line in raw_lines:
                try:
                    payload = json.loads(line)
                except Exception:
                    filtered_event_lines += 1
                    continue
                classification = _classify_ledger_event(payload)
                if classification == "realized_close":
                    realized_close_lines += 1
                elif classification == "open_event":
                    open_event_lines += 1
                elif classification == "close_nonfinal":
                    nonfinal_close_lines += 1
                else:
                    filtered_event_lines += 1
        except Exception:
            pass

    return {
        "last_synced_ts": last_ts,
        "last_synced_line": cursor.get("last_synced_line", 0),
        "freshness_lag_sec": round(lag_sec, 1),
        "ledger_total_lines": ledger_lines,
        "realized_close_lines": realized_close_lines,
        "open_event_lines": open_event_lines,
        "nonfinal_close_lines": nonfinal_close_lines,
        "filtered_event_lines": filtered_event_lines,
        "ledger_exists": ledger_exists,
        "cursor_path": str(CURSOR_FILE),
    }
