"""
Experience Updater — Phase 23.7

Applies safe mutations to an Experience based on evaluation results.
All mutations MUST be pre-approved by SafetyGuard.

Actions (in order of aggression):
  reinforce  — increase confidence, mark strategy as validated
  keep       — no change (neutral signal)
  monitor    — add monitoring flag to metadata
  downweight — decrease confidence, reduce recall priority
  deprecate  — set status to "deprecated" in metadata

Key design principle:
  The Updater never auto-generates new content (no LLM rewriting).
  It only adjusts quantitative signals (confidence, weight) and status flags.
  Content evolution (rewriting steps) is reserved for a future phase.

All mutations create a version snapshot via ExperienceVersioning before applying.
"""

import logging
import time
from typing import Optional, Tuple

from openmemo.improvement.evaluator import EvaluationResult, EvaluationEngine
from openmemo.improvement.safety import SafetyGuard, SafetyConfig, SafetyCheckResult
from openmemo.experience.models import Experience
from openmemo.experience.versioning import ExperienceVersioning

logger = logging.getLogger("openmemo.improvement.updater")

CONFIDENCE_REINFORCE_DELTA = 0.05
CONFIDENCE_DOWNWEIGHT_DELTA = 0.08
CONFIDENCE_MIN = 0.05
CONFIDENCE_MAX = 0.99


class ExperienceUpdater:
    """
    Applies safe, reversible mutations to Experience records.

    Args:
        store     — SQLiteStore
        config    — SafetyConfig (defaults to conservative Lite mode)
    """

    def __init__(self, store, config: Optional[SafetyConfig] = None):
        self._store = store
        self._safety = SafetyGuard(config=config or SafetyConfig(), store=store)
        self._evaluator = EvaluationEngine(store)
        self._versioning = ExperienceVersioning(store)

    def run_improvement_cycle(self, experience_id: str) -> dict:
        """
        Full improvement cycle for one experience:
          1. Load experience
          2. Evaluate
          3. Determine action
          4. Safety check
          5. Apply update (if allowed)
          6. Return result

        Returns:
            { experience_id, action, allowed, reason, updated, evaluation }
        """
        exp_data = self._store.get_experience(experience_id)
        if not exp_data:
            return {"error": f"experience {experience_id} not found",
                    "updated": False}

        exp = Experience.from_dict(exp_data)
        evaluation = self._evaluator.evaluate(experience_id)
        action = evaluation.recommendation

        safety_result = self._safety.check_update(
            experience_id, evaluation, action
        )

        result = {
            "experience_id": experience_id,
            "action": action,
            "allowed": safety_result.allowed,
            "reason": safety_result.reason,
            "updated": False,
            "evaluation": evaluation.to_dict(),
        }

        if not safety_result.allowed:
            logger.info(
                "[updater] exp=%s blocked: %s", experience_id, safety_result.reason
            )
            return result

        updated = self._apply(exp, action, evaluation)
        if updated:
            self._store.put_experience(updated.to_dict())
            result["updated"] = True
            result["new_confidence"] = updated.confidence

        return result

    def rollback(self, experience_id: str, target_version: int) -> dict:
        """
        Roll back an experience to a specific version.

        Always allowed (safety escape hatch).
        Requires explicit call — never triggered automatically.
        """
        safety_result = self._safety.check_rollback(
            experience_id, target_version
        )
        if not safety_result.allowed:
            return {"experience_id": experience_id,
                    "rolled_back": False,
                    "reason": safety_result.reason}

        restored = self._versioning.rollback(experience_id, target_version)
        if not restored:
            return {"experience_id": experience_id,
                    "rolled_back": False,
                    "reason": "version_not_found"}

        self._store.put_experience(restored.to_dict())
        return {
            "experience_id": experience_id,
            "rolled_back": True,
            "target_version": target_version,
            "restored_confidence": restored.confidence,
        }

    # ─── Apply ─────────────────────────────────────────────────────

    def _apply(self, exp: Experience, action: str,
               evaluation: EvaluationResult) -> Optional[Experience]:
        """Apply mutation and create version snapshot. Returns updated Experience."""
        if action == "keep":
            return None

        # Snapshot before mutation
        self._versioning.snapshot(
            exp,
            reason=f"{action} (score={evaluation.score:.3f})",
            triggered_by="evaluator",
        )

        if action == "reinforce":
            return self._reinforce(exp, evaluation)
        if action == "downweight":
            return self._downweight(exp, evaluation)
        if action == "deprecate":
            return self._deprecate(exp, evaluation)
        if action == "monitor":
            return self._monitor(exp, evaluation)

        return None

    def _reinforce(self, exp: Experience,
                   evaluation: EvaluationResult) -> Experience:
        """Increase confidence, mark as validated."""
        new_conf = min(CONFIDENCE_MAX,
                       exp.confidence + CONFIDENCE_REINFORCE_DELTA)
        exp.confidence = new_conf
        exp.metadata["evaluation_status"] = "validated"
        exp.metadata["last_eval_score"] = evaluation.score
        exp.metadata["last_improved_at"] = time.time()
        logger.info("[updater] reinforce exp=%s conf=%.3f→%.3f",
                    exp.id, exp.confidence - CONFIDENCE_REINFORCE_DELTA, new_conf)
        return exp

    def _downweight(self, exp: Experience,
                    evaluation: EvaluationResult) -> Experience:
        """Decrease confidence."""
        new_conf = max(CONFIDENCE_MIN,
                       exp.confidence - CONFIDENCE_DOWNWEIGHT_DELTA)
        exp.confidence = new_conf
        exp.metadata["evaluation_status"] = "downweighted"
        exp.metadata["last_eval_score"] = evaluation.score
        exp.metadata["last_improved_at"] = time.time()
        logger.info("[updater] downweight exp=%s conf=%.3f→%.3f",
                    exp.id, exp.confidence + CONFIDENCE_DOWNWEIGHT_DELTA, new_conf)
        return exp

    def _deprecate(self, exp: Experience,
                   evaluation: EvaluationResult) -> Experience:
        """Mark experience as deprecated."""
        exp.metadata["evaluation_status"] = "deprecated"
        exp.metadata["last_eval_score"] = evaluation.score
        exp.metadata["deprecated_at"] = time.time()
        exp.metadata["deprecation_reason"] = (
            f"score={evaluation.score:.3f} success_rate={evaluation.success_rate:.3f}"
        )
        exp.confidence = CONFIDENCE_MIN
        logger.info("[updater] deprecate exp=%s score=%.3f", exp.id, evaluation.score)
        return exp

    def _monitor(self, exp: Experience,
                 evaluation: EvaluationResult) -> Experience:
        """Add monitoring flag — no confidence change."""
        exp.metadata["evaluation_status"] = "monitoring"
        exp.metadata["last_eval_score"] = evaluation.score
        exp.metadata["last_monitored_at"] = time.time()
        return exp
