"""quality.py — Data quality checks for trading event batches.

Checks per upload batch and daily:
- Completeness: expected event rate vs actual
- Schema validation: required fields present and typed
- Anonymity: no raw tenant_id/account_id in events
- Freshness: latest event ts vs now
- Volume sanity: outlier detection
- PnL sanity: outlier detection
"""

from __future__ import annotations

import json
import os
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()

# ── Thresholds ──────────────────────────────────────────────────
MIN_EXPECTED_TRADES_PER_CYCLE = 1
MAX_VOLUME_LOTS = 10.0
MAX_PNL_ABS = 50000.0
MAX_EXEC_LATENCY_MS = 5000
FRESHNESS_LAG_THRESHOLD_SEC = 300  # 5 min


# ── Schema validation ─────────────────────────────────────────────

REQUIRED_FIELDS = {"ts", "side", "symbol", "volume", "entry", "exit", "pnl"}
NUMERIC_FIELDS = {"volume", "entry", "exit", "pnl", "confidence", "execution_latency_ms"}


def validate_schema(event: dict[str, Any]) -> list[str]:
    """Return list of schema violations for a single event."""
    errors: list[str] = []
    for field in REQUIRED_FIELDS:
        if field not in event or event[field] is None or str(event[field]).strip() == "":
            errors.append(f"missing_required_field:{field}")
    for field in NUMERIC_FIELDS:
        val = event.get(field)
        if val is not None:
            try:
                float(val)
            except (TypeError, ValueError):
                errors.append(f"invalid_numeric:{field}={val!r}")
    return errors


def validate_batch(
    batch: list[dict[str, Any]],
    min_events: int = 1,
) -> dict[str, Any]:
    """Run all quality checks on a batch of events.

    Returns result dict with pass/fail per check and overall verdict.
    """
    checks: dict[str, Any] = {}
    passed = True
    blocked_reasons: list[str] = []

    if not batch:
        return {
            "passed": False,
            "blocked_reasons": ["empty_batch"],
            "checks": {},
            "event_count": 0,
        }

    checks["event_count"] = len(batch)

    # ── Check 1: Schema validation ──────────────────────────────
    schema_errors: list[tuple[int, str]] = []  # (event_idx, error)
    for i, event in enumerate(batch[:20]):  # Check first 20 only for perf
        errors = validate_schema(event)
        for err in errors:
            schema_errors.append((i, err))

    checks["schema"] = {
        "passed": len(schema_errors) == 0,
        "errors": [(f"event[{i}]", e) for i, e in schema_errors[:10]],
        "error_count": len(schema_errors),
    }
    if schema_errors:
        passed = False
        blocked_reasons.append(f"schema_validation_failed: {schema_errors[0][1]}")

    # ── Check 2: Anonymity audit ─────────────────────────────────
    pii_issues: list[tuple[int, str]] = []
    from src.data_pipeline.anonymizer import audit_batch_for_pii
    pii_warnings = audit_batch_for_pii(batch)
    if pii_warnings:
        pii_issues = [(i, w) for i, w in enumerate(pii_warnings[:10])]
        passed = False
        blocked_reasons.append(f"pii_detected: {pii_issues[0][1]}")

    checks["anonymity"] = {
        "passed": len(pii_issues) == 0,
        "issues": pii_issues,
        "issue_count": len(pii_warnings),
    }

    # ── Check 3: Freshness ──────────────────────────────────────
    from src.data_pipeline.event_capture import _parse_ts as parse_ts
    latest_ts = None
    for event in reversed(batch):
        ts = event.get("ts")
        if ts:
            latest_ts = ts
            break

    freshness_lag = 0.0
    freshness_passed = True
    if latest_ts:
        dt = parse_ts(latest_ts)
        if dt:
            freshness_lag = (datetime.now(timezone.utc) - dt).total_seconds()
            freshness_passed = freshness_lag <= FRESHNESS_LAG_THRESHOLD_SEC

    checks["freshness"] = {
        "passed": freshness_passed,
        "latest_event_ts": latest_ts,
        "lag_sec": round(freshness_lag, 1),
        "threshold_sec": FRESHNESS_LAG_THRESHOLD_SEC,
    }
    if not freshness_passed:
        blocked_reasons.append(f"stale_data: lag={freshness_lag:.0f}s > {FRESHNESS_LAG_THRESHOLD_SEC}s")

    # ── Check 4: Volume sanity ───────────────────────────────────
    volume_outliers: list[tuple[int, float]] = []
    for i, event in enumerate(batch):
        vol = event.get("volume")
        if vol is not None:
            try:
                f = float(vol)
                if f > MAX_VOLUME_LOTS:
                    volume_outliers.append((i, f))
            except (TypeError, ValueError):
                pass

    checks["volume_sanity"] = {
        "passed": len(volume_outliers) == 0,
        "outliers": volume_outliers[:5],
        "count": len(volume_outliers),
        "threshold": MAX_VOLUME_LOTS,
    }
    if len(volume_outliers) > len(batch) * 0.1:
        passed = False
        blocked_reasons.append(f"volume_outliers: {len(volume_outliers)} events exceed {MAX_VOLUME_LOTS} lots")

    # ── Check 5: PnL sanity ──────────────────────────────────────
    pnl_outliers: list[tuple[int, float]] = []
    for i, event in enumerate(batch):
        pnl = event.get("pnl")
        if pnl is not None:
            try:
                f = float(pnl)
                if abs(f) > MAX_PNL_ABS:
                    pnl_outliers.append((i, f))
            except (TypeError, ValueError):
                pass

    checks["pnl_sanity"] = {
        "passed": len(pnl_outliers) == 0,
        "outliers": pnl_outliers[:5],
        "count": len(pnl_outliers),
        "threshold": MAX_PNL_ABS,
    }
    if len(pnl_outliers) > len(batch) * 0.05:
        passed = False
        blocked_reasons.append(f"pnl_outliers: {len(pnl_outliers)} events exceed ±${MAX_PNL_ABS}")

    # ── Check 6: Completeness ────────────────────────────────────
    # Simple completeness: at least min_events in batch
    completeness_passed = len(batch) >= min_events
    checks["completeness"] = {
        "passed": completeness_passed,
        "event_count": len(batch),
        "min_expected": min_events,
    }
    if not completeness_passed:
        blocked_reasons.append(f"insufficient_events: {len(batch)} < {min_events}")

    return {
        "passed": passed,
        "blocked_reasons": blocked_reasons,
        "event_count": len(batch),
        "checks": checks,
        "ts": datetime.now(timezone.utc).isoformat(),
    }
