"""
Improvement Worker — Phase 23.7 (Full Version)

Background worker that automatically runs the Self-Improving Loop
for all experiences in the store.

Pipeline (each cycle):
  1. List all experiences
  2. For each: check_unlock_readiness() via SafetyGuard
  3. If ready: run_improvement_cycle() → evaluate → safety check → mutate
  4. Record results (improved / blocked / skipped)
  5. Sleep for interval_seconds

Two usage modes:
  - Synchronous: ImprovementWorker.run_once() — single pass, blocking
  - Background:  ImprovementWorkerThread — daemon thread, runs every N seconds

Design principles:
  - Mutations are only applied when BOTH conditions and safety rules pass
  - Cooldown prevents thrashing — min 5 min between updates per experience
  - Worker never crashes the host process (exceptions are caught and logged)
  - Full audit: every run returns a detailed report
"""

import logging
import threading
import time
from typing import Dict, List, Optional

logger = logging.getLogger("openmemo.improvement_worker")


class ImprovementWorker:
    """
    Synchronous improvement worker — runs one full scan of all experiences.

    Args:
        store           — SQLiteStore
        config          — SafetyConfig (use SafetyConfig.full_mode() for full auto)
        agent_id        — filter experiences by agent (empty = all)
        scene           — filter experiences by scene (empty = all)
    """

    def __init__(self, store, config=None, agent_id: str = "", scene: str = ""):
        self._store = store
        self._agent_id = agent_id
        self._scene = scene

        if config is None:
            from openmemo.improvement.safety import SafetyConfig
            config = SafetyConfig.full_mode()
        self._config = config

    def run_once(self, limit: int = 500) -> Dict:
        """
        Run one improvement pass over all experiences.

        Returns:
            {
              "total_scanned": int,
              "ready_for_improvement": int,
              "improved": int,
              "blocked": int,
              "skipped": int,
              "errors": int,
              "details": [per-experience result dicts]
            }
        """
        from openmemo.improvement.updater import ExperienceUpdater
        from openmemo.improvement.safety import SafetyGuard

        updater = ExperienceUpdater(self._store, config=self._config)
        guard = SafetyGuard(config=self._config, store=self._store)

        experiences = self._store.list_experiences(
            agent_id=self._agent_id,
            scene=self._scene,
            limit=limit,
        )

        stats = {
            "total_scanned": len(experiences),
            "ready_for_improvement": 0,
            "improved": 0,
            "blocked": 0,
            "skipped": 0,
            "errors": 0,
            "details": [],
        }

        for exp_data in experiences:
            exp_id = exp_data.get("id", "")
            if not exp_id:
                stats["skipped"] += 1
                continue

            try:
                readiness = guard.check_unlock_readiness(exp_id)
                if not readiness.ready:
                    stats["skipped"] += 1
                    stats["details"].append({
                        "experience_id": exp_id,
                        "status": "skipped",
                        "reason": "conditions_not_met",
                        "blocking": readiness.blocking,
                    })
                    continue

                stats["ready_for_improvement"] += 1
                result = updater.run_improvement_cycle(exp_id)

                if result.get("updated"):
                    stats["improved"] += 1
                    result["status"] = "improved"
                elif result.get("allowed") is False:
                    stats["blocked"] += 1
                    result["status"] = "blocked"
                else:
                    stats["skipped"] += 1
                    result["status"] = "no_change"

                stats["details"].append(result)

            except Exception as e:
                logger.error(
                    "[improvement_worker] error processing exp=%s: %s", exp_id, e
                )
                stats["errors"] += 1
                stats["details"].append({
                    "experience_id": exp_id,
                    "status": "error",
                    "error": str(e),
                })

        logger.info(
            "[improvement_worker] scan complete: scanned=%d ready=%d improved=%d "
            "blocked=%d skipped=%d errors=%d",
            stats["total_scanned"], stats["ready_for_improvement"],
            stats["improved"], stats["blocked"],
            stats["skipped"], stats["errors"],
        )
        return stats

    def check_all_readiness(self, limit: int = 500) -> Dict:
        """
        Check readiness status for all experiences without running any mutations.

        Returns:
            {
              "total": int,
              "ready": int,
              "not_ready": int,
              "results": [ReadinessResult dicts]
            }
        """
        from openmemo.improvement.safety import SafetyGuard
        guard = SafetyGuard(config=self._config, store=self._store)

        experiences = self._store.list_experiences(
            agent_id=self._agent_id,
            scene=self._scene,
            limit=limit,
        )

        results = []
        ready_count = 0
        for exp_data in experiences:
            exp_id = exp_data.get("id", "")
            if not exp_id:
                continue
            r = guard.check_unlock_readiness(exp_id)
            if r.ready:
                ready_count += 1
            results.append(r.to_dict())

        return {
            "total": len(results),
            "ready": ready_count,
            "not_ready": len(results) - ready_count,
            "results": results,
        }


class ImprovementWorkerThread:
    """
    Background daemon thread that runs ImprovementWorker.run_once()
    every `interval_seconds`.

    Usage:
        worker_thread = ImprovementWorkerThread(store, interval_seconds=300)
        worker_thread.start()
        # ... later ...
        worker_thread.stop()

    The thread is a daemon — it will stop automatically when the main
    process exits. No cleanup required.
    """

    def __init__(self, store, config=None,
                 interval_seconds: float = 300.0,
                 agent_id: str = "", scene: str = ""):
        self._store = store
        self._config = config
        self._interval = interval_seconds
        self._agent_id = agent_id
        self._scene = scene
        self._thread: Optional[threading.Thread] = None
        self._stop_event = threading.Event()
        self._last_run_stats: Optional[Dict] = None
        self._run_count = 0

    def start(self):
        """Start the background improvement thread."""
        if self._thread and self._thread.is_alive():
            logger.warning("[improvement_worker] thread already running")
            return
        self._stop_event.clear()
        self._thread = threading.Thread(
            target=self._loop,
            name="ImprovementWorkerThread",
            daemon=True,
        )
        self._thread.start()
        logger.info(
            "[improvement_worker] started (interval=%.0fs)", self._interval
        )

    def stop(self):
        """Signal the thread to stop and wait for it to finish."""
        self._stop_event.set()
        if self._thread:
            self._thread.join(timeout=10)
            self._thread = None
        logger.info("[improvement_worker] stopped")

    def is_alive(self) -> bool:
        return bool(self._thread and self._thread.is_alive())

    def status(self) -> Dict:
        """Return current worker status."""
        return {
            "running": self.is_alive(),
            "interval_seconds": self._interval,
            "run_count": self._run_count,
            "last_run_stats": self._last_run_stats,
        }

    def _loop(self):
        """Main loop: run, sleep, repeat."""
        worker = ImprovementWorker(
            self._store,
            config=self._config,
            agent_id=self._agent_id,
            scene=self._scene,
        )
        while not self._stop_event.is_set():
            try:
                stats = worker.run_once()
                self._last_run_stats = stats
                self._run_count += 1
                logger.debug(
                    "[improvement_worker] run #%d done: improved=%d",
                    self._run_count, stats.get("improved", 0)
                )
            except Exception as e:
                logger.error("[improvement_worker] loop error: %s", e)

            self._stop_event.wait(timeout=self._interval)
