"""Phase 4 trust scoring, policy decisions, and retention helpers."""

from __future__ import annotations

import json
import time as _time
from pathlib import Path
from typing import Any, Dict, List, Tuple


POLICY_MODE_SHADOW = "shadow"
POLICY_MODE_ENFORCE = "enforce"
POLICY_VERSION = "phase4_trust_policy_v1"

HOT_DAYS_DEFAULT = 90
WARM_DAYS_DEFAULT = 365


def _clamp01(value: float) -> float:
    return max(0.0, min(1.0, float(value)))


def _source_weight(source_system: str, tags: List[str]) -> float:
    src = (source_system or "").lower()
    tset = {str(t).lower() for t in (tags or [])}
    if "trust_replay" in tset or "auto-closed" in tset:
        return 0.95
    if "closure-loop" in tset:
        return 0.85
    if "cortex" in src:
        return 0.5
    if src:
        return 0.75
    return 0.6


def _outcome_weight(verification: str) -> float:
    v = (verification or "").strip().lower()
    if v == "closure_achieved":
        return 1.0
    if v == "closure_likely":
        return 0.75
    if v in {"needs_manual_review", "failed"}:
        return 0.25
    return 0.5


def _freshness_weight(event_ts: int, now_ts: int | None = None) -> float:
    now = int(now_ts or _time.time())
    age_days = max(0.0, (now - int(event_ts or now)) / 86400.0)
    # Linear decay to 0.2 by 365 days.
    if age_days >= 365.0:
        return 0.2
    return 1.0 - (0.8 * (age_days / 365.0))


def classify_retention_tier(
    *,
    event_ts: int,
    now_ts: int | None = None,
    hot_days: int = HOT_DAYS_DEFAULT,
    warm_days: int = WARM_DAYS_DEFAULT,
) -> str:
    now = int(now_ts or _time.time())
    age_days = max(0.0, (now - int(event_ts or now)) / 86400.0)
    if age_days <= float(hot_days):
        return "hot"
    if age_days <= float(warm_days):
        return "warm"
    return "archive"


def compute_trust_score(event: Dict[str, Any], *, now_ts: int | None = None) -> Dict[str, Any]:
    confidence = _clamp01(float(event.get("confidence", 0.6) or 0.6))
    source = _source_weight(str(event.get("source_system", "")), list(event.get("tags", []) or []))
    freshness = _freshness_weight(int(event.get("event_ts", 0) or 0), now_ts=now_ts)
    outcome = _outcome_weight(str(event.get("verification", "pending")))
    # Weighted deterministic score.
    score = _clamp01((0.4 * confidence) + (0.25 * source) + (0.2 * freshness) + (0.15 * outcome))
    return {
        "trust_score": round(score, 4),
        "trust_factors": {
            "confidence": round(confidence, 4),
            "source_quality": round(source, 4),
            "freshness": round(freshness, 4),
            "outcome": round(outcome, 4),
        },
    }


def compute_policy_decision(
    *,
    trust_score: float,
    risk_tier: str,
    fix_type: str,
    policy_mode: str = POLICY_MODE_SHADOW,
) -> Dict[str, Any]:
    risk = (risk_tier or "").strip().lower()
    ftype = (fix_type or "").strip().lower()
    mode = (policy_mode or POLICY_MODE_SHADOW).strip().lower()
    if mode not in {POLICY_MODE_SHADOW, POLICY_MODE_ENFORCE}:
        mode = POLICY_MODE_SHADOW

    reasons: List[str] = []
    decision = "manual_review_required"
    approval_gate = "required"
    score = _clamp01(trust_score)

    if ftype == "cortex_ai":
        decision = "manual_review_required"
        reasons.append("cortex_fix_requires_manual_review")
    elif risk == "high":
        decision = "manual_review_required"
        reasons.append("high_risk_requires_manual_review")
    elif score >= 0.8:
        decision = "auto_closure_candidate"
        approval_gate = "not_required"
        reasons.append("high_trust_score")
    elif score >= 0.72:
        decision = "closure_likely_with_followup"
        approval_gate = "not_required" if risk in {"low", "medium", ""} else "required"
        reasons.append("medium_trust_score")
    else:
        decision = "manual_review_required"
        reasons.append("low_trust_score")

    return {
        "policy_mode": mode,
        "policy_version": POLICY_VERSION,
        "policy_decision": decision,
        "policy_reason_codes": reasons,
        "approval_gate": approval_gate,
    }


def load_runtime_events(path: str) -> List[Dict[str, Any]]:
    p = Path(path)
    if not p.exists():
        return []
    try:
        payload = json.loads(p.read_text(encoding="utf-8"))
    except Exception:
        return []
    if not isinstance(payload, list):
        return []
    return [r for r in payload if isinstance(r, dict)]


def load_closure_metrics(path: str) -> List[Dict[str, Any]]:
    p = Path(path)
    if not p.exists():
        return []
    try:
        payload = json.loads(p.read_text(encoding="utf-8"))
    except Exception:
        return []
    if not isinstance(payload, list):
        return []
    return [r for r in payload if isinstance(r, dict)]


def get_retention_status(
    *,
    runtime_events_path: str,
    hot_days: int = HOT_DAYS_DEFAULT,
    warm_days: int = WARM_DAYS_DEFAULT,
    now_ts: int | None = None,
) -> Dict[str, Any]:
    rows = load_runtime_events(runtime_events_path)
    now = int(now_ts or _time.time())
    counts = {"hot": 0, "warm": 0, "archive": 0}
    oldest = {"hot": None, "warm": None, "archive": None}
    newest = {"hot": None, "warm": None, "archive": None}
    for row in rows:
        ts = int(row.get("event_ts", 0) or 0)
        tier = classify_retention_tier(event_ts=ts, now_ts=now, hot_days=hot_days, warm_days=warm_days)
        counts[tier] += 1
        if oldest[tier] is None or ts < oldest[tier]:
            oldest[tier] = ts
        if newest[tier] is None or ts > newest[tier]:
            newest[tier] = ts
    return {
        "status": "ok",
        "total_events": len(rows),
        "tiers": counts,
        "oldest_event_ts": oldest,
        "newest_event_ts": newest,
        "hot_days": hot_days,
        "warm_days": warm_days,
    }


def get_trust_metrics(
    *,
    runtime_events_path: str,
    closure_metrics_path: str,
    now_ts: int | None = None,
) -> Dict[str, Any]:
    rows = load_runtime_events(runtime_events_path)
    metrics = load_closure_metrics(closure_metrics_path)
    if not rows:
        return {
            "status": "ok",
            "total_events": 0,
            "avg_trust_score": 0.0,
            "policy_decisions": {},
            "shadow_vs_enforced_delta": 0,
            "retention": get_retention_status(runtime_events_path=runtime_events_path, now_ts=now_ts),
            "closure_runs": len(metrics),
        }
    scores = [float(r.get("trust_score", 0.0) or 0.0) for r in rows]
    decisions: Dict[str, int] = {}
    for row in rows:
        dec = str(row.get("policy_decision", "unknown"))
        decisions[dec] = decisions.get(dec, 0) + 1
    return {
        "status": "ok",
        "total_events": len(rows),
        "avg_trust_score": round(sum(scores) / max(1, len(scores)), 4),
        "policy_decisions": decisions,
        "shadow_vs_enforced_delta": 0,
        "retention": get_retention_status(runtime_events_path=runtime_events_path, now_ts=now_ts),
        "closure_runs": len(metrics),
    }


def explain_trust_event(
    *,
    event_key: str,
    runtime_events_path: str,
) -> Dict[str, Any]:
    rows = load_runtime_events(runtime_events_path)
    for row in rows:
        if str(row.get("event_key", "")) == str(event_key):
            return {
                "status": "ok",
                "event_key": event_key,
                "tenant_id": row.get("tenant_id", "default"),
                "trust_score": row.get("trust_score", 0.0),
                "trust_factors": row.get("trust_factors", {}),
                "policy_decision": row.get("policy_decision", "unknown"),
                "policy_reason_codes": row.get("policy_reason_codes", []),
                "policy_mode": row.get("policy_mode", POLICY_MODE_SHADOW),
                "retention_tier": row.get("retention_tier", "hot"),
                "event": row,
            }
    return {
        "status": "error",
        "error": f"event_key '{event_key}' not found",
        "error_code": "not_found",
    }
