"""Scoped proactive profile load/save — never writes under state/subject."""

from __future__ import annotations

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

from niannian.chat.proactive_scope import ProactiveScope


def _utc_now_iso() -> str:
    return datetime.now(timezone.utc).isoformat()


DEFAULT_PROFILE: dict[str, Any] = {
    "interval_multiplier": 1.0,
    "avoid_topics": [],
    "ignored_streak": 0,
    "explicit_bias": 0,
    "responded_rate_7d": 0.5,
    "version": 1,
}


def assert_not_subject_write(path: Path) -> None:
    """Raise if path resolves under state/subject (L0 主体污染门禁)."""
    resolved = Path(path).resolve()
    parts = resolved.parts
    for i, part in enumerate(parts):
        if part == "state" and i + 1 < len(parts) and parts[i + 1] == "subject":
            raise ValueError(
                f"proactive profile must not write under state/subject: {path}"
            )


def load_proactive_profile(scope: ProactiveScope) -> dict[str, Any]:
    path = scope.profile_path
    if path.is_file():
        data = json.loads(path.read_text(encoding="utf-8"))
        if not isinstance(data, dict):
            raise ValueError(f"proactive profile must be a JSON object: {path}")
        return data
    return {**DEFAULT_PROFILE, "updated_at": _utc_now_iso()}


def save_proactive_profile(scope: ProactiveScope, data: dict[str, Any]) -> None:
    path = scope.profile_path
    assert_not_subject_write(path)
    path.parent.mkdir(parents=True, exist_ok=True)
    payload = dict(data)
    payload["updated_at"] = _utc_now_iso()
    path.write_text(
        json.dumps(payload, ensure_ascii=False, indent=2) + "\n",
        encoding="utf-8",
    )


def apply_proactive_feedback(
    scope: ProactiveScope,
    profile: dict[str, Any],
    *,
    rating: str,
    topic: str | None = None,
) -> dict[str, Any]:
    """Update scoped profile from explicit user feedback (quiet / ok / more)."""
    updated = dict(profile)
    mult = float(updated.get("interval_multiplier") or 1.0)
    bias = int(updated.get("explicit_bias") or 0)
    key = str(rating or "ok").strip().lower()
    if key == "quiet":
        bias -= 1
        mult = min(mult + 0.25, 2.5)
        if topic:
            avoid = list(updated.get("avoid_topics") or [])
            if topic not in avoid:
                avoid.append(topic)
            updated["avoid_topics"] = avoid[-20:]
    elif key == "more":
        bias += 1
        mult = max(mult * 0.85, 0.6)
    updated["explicit_bias"] = max(-3, min(3, bias))
    updated["interval_multiplier"] = mult
    save_proactive_profile(scope, updated)
    return updated
