# Familiar - Self-Hosted AI Companion Platform
# Copyright (c) 2026 George Scott Foley
# ORCID: 0009-0006-4957-0540
# Email: Georgescottfoley@proton.me
# Licensed under the MIT License - see LICENSE file for details

"""Installation knowledge capture and recall.

After a successful install/setup tool execution, this module writes a
markdown memory documenting the what/how/platform details.  Those memories
are:

  1. Stored locally under ``~/.familiar/data/memories/training/practical/
     system_administration/``
  2. Marked ``training_eligible: true`` so the co-op training corpus
     pipeline can pick them up
  3. Shareable via the mesh markdown memory bridge for cross-agent learning

Before a new install, ``search_install_knowledge()`` checks whether the
agent has done this before, letting it reuse the known-good method.
"""

import json
import logging
import platform
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional

logger = logging.getLogger(__name__)

# Where install memories live (follows training scaffold domain/subdomain layout)
INSTALL_MEMORY_DIR = (
    Path.home()
    / ".familiar"
    / "data"
    / "memories"
    / "training"
    / "practical"
    / "system_administration"
)

CURRENT_CONSENT_VERSION = "1.0"

# Difficulty heuristic by install method
_METHOD_DIFFICULTY = {
    "dnf": "beginner",
    "apt-get": "beginner",
    "apt": "beginner",
    "brew": "beginner",
    "pip": "beginner",
    "flatpak": "beginner",
    "snap": "beginner",
    "pacman": "intermediate",
    "zypper": "intermediate",
    "apk": "intermediate",
    "binary": "intermediate",
    "appimage": "intermediate",
    "script": "advanced",
}


def _get_platform_string() -> str:
    """Return a human-readable platform string."""
    parts = [platform.system(), platform.release(), platform.machine()]
    try:
        with open("/etc/os-release") as f:
            for line in f:
                if line.startswith("PRETTY_NAME="):
                    parts.insert(0, line.split("=", 1)[1].strip().strip('"'))
                    break
    except (OSError, FileNotFoundError):
        pass
    return " | ".join(parts)


def capture_install_knowledge(
    tool_name: str,
    tool_input: Dict[str, Any],
    tool_result: Any,
) -> Optional[Path]:
    """Capture a successful installation as a markdown memory.

    Args:
        tool_name: The tool that was executed (install_package, setup_binary,
            run_setup_script).
        tool_input: The tool's input dict (packages, url, name, etc.).
        tool_result: The tool's output — either a dict or a JSON string.

    Returns:
        Path to the created memory file, or None if skipped.
    """
    # Parse result if it's a JSON string
    if isinstance(tool_result, str):
        try:
            result = json.loads(tool_result)
        except (json.JSONDecodeError, TypeError):
            return None
    else:
        result = tool_result

    if not isinstance(result, dict):
        return None

    # Only capture successes
    if tool_name == "install_package" and not result.get("installed"):
        return None
    if tool_name == "setup_binary" and not result.get("installed"):
        return None
    if tool_name == "run_setup_script" and not result.get("success"):
        return None

    # Extract fields based on tool type
    if tool_name == "install_package":
        packages = result.get("packages", [])
        if isinstance(packages, str):
            packages = [packages]
        package_name = ", ".join(packages) if packages else "unknown"
        method = result.get("manager", "auto")
        command = f"{method} install {package_name}"
        output_summary = (result.get("output") or "")[:500]
        reason = (tool_input.get("reason") or "").strip()

    elif tool_name == "setup_binary":
        package_name = result.get("name") or tool_input.get("name", "binary")
        method = "binary"
        url = tool_input.get("url", "")
        command = f"download {url} -> {result.get('path', '~/.local/bin/')}"
        output_summary = result.get("message", "")
        reason = (tool_input.get("reason") or "").strip()

    elif tool_name == "run_setup_script":
        script = result.get("script") or tool_input.get("script_path", "script")
        package_name = Path(script).stem
        method = "script"
        interpreter = tool_input.get("interpreter", "bash")
        args = tool_input.get("args", [])
        args_str = " ".join(args) if isinstance(args, list) else str(args)
        command = f"{interpreter} {script} {args_str}".strip()
        output_summary = (result.get("output") or "")[:500]
        reason = (tool_input.get("reason") or "").strip()

    else:
        return None

    platform_str = _get_platform_string()
    difficulty = _METHOD_DIFFICULTY.get(method, "intermediate")
    now = datetime.now(timezone.utc).isoformat()

    # Build markdown with training frontmatter
    content = f"""---
topic: "Installing {package_name} on {platform.system()}"
domain: "practical"
subdomain: "system_administration"
instruction_types:
  - "how_to"
difficulty: "{difficulty}"
audience: "general"
language: "en"
training_eligible: true
consent_version: "{CURRENT_CONSENT_VERSION}"
pair_type: "howto"
quality: 0.8
revision: 1
created_at: "{now}"
updated_at: "{now}"
install_method: "{method}"
platform: "{platform_str}"
tool_name: "{tool_name}"
pairs:
  - instruction: "How do I install {package_name}?"
    response: "Use {method}: `{command}`"
    instruction_type: "how_to"
---

# Installing {package_name}

## Environment
- Platform: {platform_str}
- Install method: {method}
- Date: {now[:10]}

## Command
```bash
{command}
```

## Result
{output_summary}
"""
    if reason:
        content += f"\n## Reason\n{reason}\n"

    # Write to disk
    try:
        INSTALL_MEMORY_DIR.mkdir(parents=True, exist_ok=True)
        slug = package_name.lower().replace(" ", "-").replace(",", "")[:60]
        slug = "".join(c for c in slug if c.isalnum() or c == "-")
        timestamp = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S")
        file_path = INSTALL_MEMORY_DIR / f"{slug}-{timestamp}.md"

        # Don't overwrite
        counter = 1
        while file_path.exists():
            file_path = INSTALL_MEMORY_DIR / f"{slug}-{timestamp}-{counter}.md"
            counter += 1

        file_path.write_text(content, encoding="utf-8")
        logger.info("Install knowledge captured: %s -> %s", package_name, file_path)
        return file_path

    except Exception as e:
        logger.debug("Failed to capture install knowledge: %s", e)
        return None


def search_install_knowledge(query: str) -> List[Dict[str, Any]]:
    """Search local install memories for prior successful installs.

    Args:
        query: Package or tool name to search for.

    Returns:
        List of dicts with topic, install_method, platform, command,
        notes, file_path — sorted by recency (newest first).
    """
    if not query or not INSTALL_MEMORY_DIR.exists():
        return []

    query_lower = query.lower()
    results = []

    try:
        for md_file in INSTALL_MEMORY_DIR.glob("*.md"):
            text = md_file.read_text(encoding="utf-8")

            # Quick filename check first
            if query_lower not in md_file.stem.lower() and query_lower not in text.lower():
                continue

            # Parse frontmatter
            entry: Dict[str, Any] = {"file_path": str(md_file)}
            if text.startswith("---"):
                parts = text.split("---", 2)
                if len(parts) >= 3:
                    try:
                        import yaml

                        fm = yaml.safe_load(parts[1])
                        if isinstance(fm, dict):
                            entry["topic"] = fm.get("topic", "")
                            entry["install_method"] = fm.get("install_method", "")
                            entry["platform"] = fm.get("platform", "")
                            entry["difficulty"] = fm.get("difficulty", "")
                            entry["quality"] = fm.get("quality", 0.0)
                            entry["created_at"] = fm.get("created_at", "")
                    except Exception:
                        pass

            # Extract command from markdown body
            cmd_start = text.find("```bash")
            if cmd_start != -1:
                cmd_end = text.find("```", cmd_start + 7)
                if cmd_end != -1:
                    entry["command"] = text[cmd_start + 7 : cmd_end].strip()

            # Extract result section
            result_start = text.find("## Result")
            if result_start != -1:
                next_section = text.find("\n## ", result_start + 10)
                if next_section == -1:
                    next_section = len(text)
                entry["notes"] = text[result_start + 10 : next_section].strip()[:300]

            results.append(entry)

    except Exception as e:
        logger.debug("Install knowledge search failed: %s", e)

    # Sort by created_at descending (newest first)
    results.sort(key=lambda r: r.get("created_at", ""), reverse=True)
    return results
