"""anonymizer.py — Field-level sanitization for trading data before upload.

Privacy controls:
- tenant_id: SHA256 hash (10 chars) with ANON_SALT
- ticket: Sequential internal ID (no MT5 ticket exposed)
- account_id: Removed entirely
- timestamp: Rounded to 1-minute precision
- volume/entry/exit: Quantized to 3 significant figures
- All other fields passed through as-is
"""

from __future__ import annotations

import hashlib
import os
from datetime import datetime, timezone
from typing import Any

import config_manager

ANON_SALT = os.environ.get("ANON_SALT", "LOBSTER_QUANT_ANON_V1")
SERVER_BASE_URL = os.environ.get("DATA_PIPELINE_SERVER_URL", "https://43.167.210.121:8000")

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

# Tenant ID counter (persisted to avoid restart resets)
_TENANT_COUNTER_FILE = DATA_DIR / "uploads" / ".tenant_counter.json"


# ── Tenant ID hashing ────────────────────────────────────────────────

def hash_tenant_id(tenant_id: str) -> str:
    """SHA256(ANON_SALT + tenant_id)[:10] — one-way, stable across restarts."""
    raw = f"{ANON_SALT}{tenant_id}"
    return hashlib.sha256(raw.encode("utf-8")).hexdigest()[:10]


# ── Sequential ID mapping ───────────────────────────────────────────

def _load_counter() -> dict[str, int]:
    try:
        import json as _json
        path = _TENANT_COUNTER_FILE
        if path.exists():
            return _json.loads(path.read_text(encoding="utf-8"))
    except Exception:
        pass
    return {}


def _save_counter(counter: dict[str, int]) -> None:
    try:
        import json as _json
        _TENANT_COUNTER_FILE.parent.mkdir(parents=True, exist_ok=True)
        _TENANT_COUNTER_FILE.write_text(_json.dumps(counter, indent=2), encoding="utf-8")
    except Exception:
        pass


def _next_seq_id(tenant_id: str) -> int:
    """Get next sequential ID for a tenant, starting from 1."""
    counter = _load_counter()
    next_val = counter.get(tenant_id, 0) + 1
    counter[tenant_id] = next_val
    _save_counter(counter)
    return next_val


# ── Timestamp quantization ─────────────────────────────────────────

def _quantize_ts(ts: Any) -> str:
    """Round timestamp to 1-minute precision."""
    from src.data_pipeline.event_capture import _parse_ts
    dt = _parse_ts(ts)
    if dt is None:
        return str(ts) if ts else ""
    # Round down to nearest minute
    dt_rounded = dt.replace(second=0, microsecond=0)
    return dt_rounded.isoformat()


# ── Numeric quantization ────────────────────────────────────────────

def _sig_fig(value: Any, sig: int = 3) -> float | None:
    """Round to sig significant figures."""
    try:
        f = float(value)
    except (TypeError, ValueError):
        return None
    if f == 0:
        return 0.0
    magnitude = abs(f)
    if magnitude >= 1:
        # Round to sig-1 decimal places
        decimals = max(0, sig - 1 - int(__import__("math").floor(__import__("math").log10(magnitude))))
        return round(f, decimals)
    else:
        # Round to sig significant figures
        return round(f, sig - 1 - int(__import__("math").floor(__import__("math").log10(magnitude))))


# ── Core anonymization ────────────────────────────────────────────

def anonymize_event(event: dict[str, Any], tenant_id: str) -> dict[str, Any]:
    """Apply full anonymization pipeline to a single trade event.

    Args:
        event: Raw trade event dict from trading_ledger.ndjson
        tenant_id: Raw tenant identifier

    Returns anonymized event dict suitable for upload.
    """
    anon = {}

    # ── Identity fields ────────────────────────────────────────────
    anon["tenant_id_hash"] = hash_tenant_id(tenant_id)
    anon["seq_id"] = _next_seq_id(tenant_id)

    # ── Time ───────────────────────────────────────────────────────
    ts_raw = event.get("ts") or event.get("timestamp") or ""
    anon["ts"] = _quantize_ts(ts_raw)

    # ── Trade fields (quantized) ──────────────────────────────────
    vol = _sig_fig(event.get("volume"), 3)
    if vol is not None:
        anon["volume"] = round(vol, 3)

    entry = _sig_fig(event.get("entry"), 3)
    if entry is not None:
        anon["entry"] = round(entry, 3)

    exit_val = _sig_fig(event.get("exit"), 3)
    if exit_val is not None:
        anon["exit"] = round(exit_val, 3)

    pnl = event.get("pnl")
    if pnl is not None:
        anon["pnl"] = round(float(pnl), 2)

    # ── Categorical fields (passed through, no PII) ────────────────
    for field in ("side", "symbol", "regime", "order_type", "action", "status",
                  "decision_id", "intent_id", "signal_id", "close_reason"):
        val = event.get(field)
        if val is not None and str(val).strip():
            anon[field] = str(val).strip()

    # ── Numeric metadata (quantized) ──────────────────────────────
    conf = event.get("confidence")
    if conf is not None:
        try:
            anon["confidence"] = round(float(conf), 3)
        except (TypeError, ValueError):
            pass

    lat = event.get("execution_latency_ms")
    if lat is not None:
        try:
            anon["execution_latency_ms"] = round(float(lat))
        except (TypeError, ValueError):
            pass

    return anon


def anonymize_batch(
    events: list[dict[str, Any]],
    tenant_id: str,
) -> list[dict[str, Any]]:
    """Apply anonymization to a batch of events."""
    return [anonymize_event(e, tenant_id) for e in events]


# ── Privacy audit ─────────────────────────────────────────────────

PII_FIELDS = {"account_id", "account", "user_id", "name", "email", "phone",
              "machine_id", "mac_address", "ip_address"}

PROHIBITED_PATTERNS = [
    ("account_id", r"ACC[0-9A-Z]{8,}"),
    ("mac_address", r"([0-9A-Fa-f]{2}:){5}[0-9A-Fa-f]{2}"),
    ("email", r"[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+"),
]


def audit_batch_for_pii(batch: list[dict[str, Any]]) -> list[str]:
    """Check a batch for accidental PII leakage.

    Returns a list of warnings (empty = clean).
    """
    import re
    warnings: list[str] = []
    for i, event in enumerate(batch):
        # Check for prohibited field names
        for field in event:
            if field.lower() in PII_FIELDS:
                warnings.append(f"Event {i}: prohibited field '{field}' detected")
        # Check for PII patterns in values
        for field, pattern in PROHIBITED_PATTERNS:
            for k, v in event.items():
                if isinstance(v, str) and re.search(pattern, v, re.IGNORECASE):
                    warnings.append(f"Event {i}: '{field}' pattern found in '{k}'")
    return warnings
