"""Distillation mixin — extracts lasting facts from recent thoughts and monitors memory."""

import logging
from pathlib import Path

from config import settings
from timmy.thinking.seeds import _META_OBSERVATION_PHRASES, _SENSITIVE_PATTERNS

logger = logging.getLogger(__name__)


class _DistillationMixin:
    """Mixin providing fact-distillation and memory-monitoring behaviour.

    Expects the host class to provide:
    - self.count_thoughts() -> int
    - self.get_recent_thoughts(limit) -> list[Thought]
    - self._call_agent(prompt) -> str  (async)
    """

    def _should_distill(self) -> bool:
        """Check if distillation should run based on interval and thought count."""
        interval = settings.thinking_distill_every
        if interval <= 0:
            return False

        count = self.count_thoughts()
        if count == 0 or count % interval != 0:
            return False

        return True

    def _build_distill_prompt(self, thoughts) -> str:
        """Build the prompt for extracting facts from recent thoughts."""
        thought_text = "\n".join(f"- [{t.seed_type}] {t.content}" for t in reversed(thoughts))

        return (
            "You are reviewing your own recent thoughts. Extract 0-3 facts "
            "worth remembering long-term.\n\n"
            "GOOD facts (store these):\n"
            "- User preferences: 'Alexander prefers YAML config over code changes'\n"
            "- Project decisions: 'Switched from hardcoded personas to agents.yaml'\n"
            "- Learned knowledge: 'Ollama supports concurrent model loading'\n"
            "- User information: 'Alexander is interested in Bitcoin and sovereignty'\n\n"
            "BAD facts (never store these):\n"
            "- Self-referential observations about your own thinking process\n"
            "- Meta-commentary about your memory, timestamps, or internal state\n"
            "- Observations about being idle or having no chat messages\n"
            "- File paths, tokens, API keys, or any credentials\n"
            "- Restatements of your standing rules or system prompt\n\n"
            "Return ONLY a JSON array of strings. If nothing is worth saving, "
            "return []. Be selective — only store facts about the EXTERNAL WORLD "
            "(the user, the project, technical knowledge), never about your own "
            "internal process.\n\n"
            f"Recent thoughts:\n{thought_text}\n\nJSON array:"
        )

    def _parse_facts_response(self, raw: str) -> list[str]:
        """Parse JSON array from LLM response, stripping markdown fences.

        Resilient to models that prepend reasoning text or wrap the array in
        prose.  Finds the first ``[...]`` block and parses that.
        """
        if not raw or not raw.strip():
            return []

        import json

        cleaned = raw.strip()

        # Strip markdown code fences
        if cleaned.startswith("```"):
            cleaned = cleaned.split("\n", 1)[-1].rsplit("```", 1)[0].strip()

        # Try direct parse first (fast path)
        try:
            facts = json.loads(cleaned)
            if isinstance(facts, list):
                return [f for f in facts if isinstance(f, str)]
        except (json.JSONDecodeError, ValueError):
            pass

        # Fallback: extract first JSON array from the text
        start = cleaned.find("[")
        if start == -1:
            return []
        # Walk to find the matching close bracket
        depth = 0
        for i, ch in enumerate(cleaned[start:], start):
            if ch == "[":
                depth += 1
            elif ch == "]":
                depth -= 1
                if depth == 0:
                    try:
                        facts = json.loads(cleaned[start : i + 1])
                        if isinstance(facts, list):
                            return [f for f in facts if isinstance(f, str)]
                    except (json.JSONDecodeError, ValueError):
                        pass
                    break
        return []

    def _filter_and_store_facts(self, facts: list[str]) -> None:
        """Filter and store valid facts, blocking sensitive and meta content."""
        from timmy.memory_system import memory_write

        for fact in facts[:3]:  # Safety cap
            if not isinstance(fact, str) or len(fact.strip()) <= 10:
                continue

            fact_lower = fact.lower()

            # Block sensitive information
            if any(pat in fact_lower for pat in _SENSITIVE_PATTERNS):
                logger.warning("Distill: blocked sensitive fact: %s", fact[:60])
                continue

            # Block self-referential meta-observations
            if any(phrase in fact_lower for phrase in _META_OBSERVATION_PHRASES):
                logger.debug("Distill: skipped meta-observation: %s", fact[:60])
                continue

            result = memory_write(fact.strip(), context_type="fact")
            logger.info("Distilled fact: %s → %s", fact[:60], result[:40])

    def _maybe_check_memory(self) -> None:
        """Every N thoughts, check memory status and log it.

        Prevents unmonitored memory bloat during long thinking sessions
        by periodically calling get_memory_status and logging the results.
        """
        try:
            interval = settings.thinking_memory_check_every
            if interval <= 0:
                return

            count = self.count_thoughts()
            if count == 0 or count % interval != 0:
                return

            from timmy.tools_intro import get_memory_status

            status = get_memory_status()
            hot = status.get("tier1_hot_memory", {})
            vault = status.get("tier2_vault", {})
            logger.info(
                "Memory status check (thought #%d): hot_memory=%d lines, vault=%d files",
                count,
                hot.get("line_count", 0),
                vault.get("file_count", 0),
            )
        except Exception as exc:
            logger.warning("Memory status check failed: %s", exc)

    async def _maybe_distill(self) -> None:
        """Every N thoughts, extract lasting insights and store as facts."""
        try:
            if not self._should_distill():
                return

            interval = settings.thinking_distill_every
            recent = self.get_recent_thoughts(limit=interval)
            if len(recent) < interval:
                return

            raw = await self._call_agent(self._build_distill_prompt(recent))
            if facts := self._parse_facts_response(raw):
                self._filter_and_store_facts(facts)
        except Exception as exc:
            logger.warning("Thought distillation failed: %s", exc)

    def _maybe_check_memory_status(self) -> None:
        """Every N thoughts, run a proactive memory status audit and log results."""
        try:
            interval = settings.thinking_memory_check_every
            if interval <= 0:
                return

            count = self.count_thoughts()
            if count == 0 or count % interval != 0:
                return

            from timmy.tools_intro import get_memory_status

            status = get_memory_status()

            # Log summary at INFO level
            tier1 = status.get("tier1_hot_memory", {})
            tier3 = status.get("tier3_semantic", {})
            hot_lines = tier1.get("line_count", "?")
            vectors = tier3.get("vector_count", "?")
            logger.info(
                "Memory audit (thought #%d): hot_memory=%s lines, semantic=%s vectors",
                count,
                hot_lines,
                vectors,
            )

            # Write to memory_audit.log for persistent tracking
            from datetime import UTC, datetime

            audit_path = Path("data/memory_audit.log")
            audit_path.parent.mkdir(parents=True, exist_ok=True)
            timestamp = datetime.now(UTC).isoformat(timespec="seconds")
            with audit_path.open("a") as f:
                f.write(
                    f"{timestamp}  thought={count}  "
                    f"hot_lines={hot_lines}  "
                    f"vectors={vectors}  "
                    f"vault_files={status.get('tier2_vault', {}).get('file_count', '?')}\n"
                )
        except Exception as exc:
            logger.warning("Memory status check failed: %s", exc)
