"""
Armorer Knowledge Base — a collection of domain-specific knowledge files
that are injected into Armorer prompts for better decision-making.

Knowledge files are plain Markdown (.md) stored in this directory.
Each file represents a topic (e.g. docker.md, security.md).

Usage:
    from armorer.knowledge import KnowledgeBase

    kb = KnowledgeBase()
    kb.get("docker")                    # single topic
    kb.get("docker", "security")        # multiple topics
    kb.get_all()                        # everything
    kb.search("volume permissions")     # keyword search across all topics
    kb.topics()                         # list available topic names
"""

import re
import datetime
from pathlib import Path
from typing import Optional

_KB_DIR = Path(__file__).parent
_USER_KB_DIR = Path.home() / ".armorer" / "knowledge"


class KnowledgeBase:
    """Load and query Armorer's built-in and user-contributed knowledge files."""

    def __init__(self, extra_dirs: Optional[list[Path]] = None):
        """
        Automatically loads knowledge from:
          1. Built-in KB directory (this package)
          2. User KB directory (~/.armorer/knowledge/)
          3. Any extra_dirs passed explicitly

        Args:
            extra_dirs: Additional directories to scan for .md knowledge files.
                        Useful for project-level overrides or user-added knowledge.
        """
        self._dirs: list[Path] = [_KB_DIR]

        # Auto-include user KB directory if it exists
        if _USER_KB_DIR.is_dir():
            self._dirs.append(_USER_KB_DIR)

        if extra_dirs:
            self._dirs.extend(extra_dirs)

    # ── Public API ───────────────────────────────────────────────────────────

    def topics(self) -> list[str]:
        """Return sorted list of available topic names (without .md extension)."""
        seen = set()
        for d in self._dirs:
            if d.is_dir():
                for f in d.glob("*.md"):
                    seen.add(f.stem)
        return sorted(seen)

    def get(self, *topic_names: str) -> str:
        """Load one or more knowledge topics by name. Returns combined text."""
        parts = []
        for name in topic_names:
            content = self._load_topic(name)
            if content:
                parts.append(content)
        return "\n\n".join(parts)

    def get_all(self) -> str:
        """Load every knowledge topic. Useful for building comprehensive prompts."""
        return self.get(*self.topics())

    def search(self, query: str, max_chars: int = 4000) -> str:
        """Keyword search across all knowledge files.

        Returns the most relevant sections (paragraphs) that contain
        any of the query words, up to *max_chars*.
        """
        query_words = set(re.findall(r'\w+', query.lower()))
        if not query_words:
            return ""

        scored_sections: list[tuple[int, str, str]] = []

        for topic in self.topics():
            content = self._load_topic(topic)
            if not content:
                continue
            # Split into sections by markdown headings
            sections = re.split(r'(?=^#{1,3} )', content, flags=re.MULTILINE)
            for section in sections:
                section = section.strip()
                if not section:
                    continue
                words = set(re.findall(r'\w+', section.lower()))
                score = len(query_words & words)
                if score > 0:
                    scored_sections.append((score, topic, section))

        # Sort by relevance (highest score first)
        scored_sections.sort(key=lambda x: x[0], reverse=True)

        result_parts = []
        chars = 0
        for score, topic, section in scored_sections:
            if chars + len(section) > max_chars:
                break
            result_parts.append(f"[{topic}]\n{section}")
            chars += len(section)

        return "\n\n".join(result_parts)

    def save_fix(self, error_pattern: str, diagnosis: str, fix: str,
                 topic: str = "learned_fixes") -> bool:
        """Persist a successful repair pattern as a new KB entry.

        Writes to the user KB directory (~/.armorer/knowledge/) so that
        future repair sessions can learn from past successes.

        Args:
            error_pattern: The error message or pattern that was fixed.
            diagnosis:     What caused the problem.
            fix:           What resolved it.
            topic:         KB file name to append to (default: learned_fixes).
        """
        try:
            _USER_KB_DIR.mkdir(parents=True, exist_ok=True)
            filepath = _USER_KB_DIR / f"{topic}.md"

            timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M")
            entry = (
                f"\n\n### {error_pattern[:80]}\n"
                f"- **When**: {timestamp}\n"
                f"- **Cause**: {diagnosis}\n"
                f"- **Fix**: {fix}\n"
            )

            # Create or append
            if filepath.exists():
                with open(filepath, "a") as f:
                    f.write(entry)
            else:
                with open(filepath, "w") as f:
                    f.write(f"# Learned Fixes\n\nAutomatically recorded by Armorer.\n{entry}")

            # Ensure the user dir is in our search path
            if _USER_KB_DIR not in self._dirs:
                self._dirs.append(_USER_KB_DIR)

            return True
        except Exception:
            return False

    # ── Internal ─────────────────────────────────────────────────────────────

    def _load_topic(self, name: str) -> Optional[str]:
        """Load a single topic file. Searches all dirs, last-wins for overrides."""
        content = None
        for d in self._dirs:
            filepath = d / f"{name}.md"
            if filepath.exists() and filepath.is_file():
                try:
                    content = filepath.read_text(errors="replace").strip()
                except Exception:
                    pass
        return content
