"""Export memories as AI context files for different IDE / agent tools."""
from __future__ import annotations

import os
import tempfile
from collections import defaultdict
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from .config import load_config
from .store import list_memories

_HEADER = (
    "> Auto-generated by `lore export` on {date}. "
    "Do not edit manually — use `lore add` to update.\n"
)

_OWASP_ITEMS = [
    "Broken access control",
    "Cryptographic failures",
    "Injection attacks (SQL, XSS, command injection)",
    "Insecure design",
    "Security misconfiguration",
    "Vulnerable and outdated components",
    "Identification and authentication failures",
    "Software and data integrity failures",
    "Security logging and monitoring failures",
    "Server-side request forgery (SSRF)",
]


def _render_repo_context(root: Path) -> str:
    """Build a Markdown repository context block from live git metadata."""
    try:
        from .extract import git_context
        ctx = git_context(root)
    except Exception:
        ctx = {}
    if not ctx:
        return ""

    lines: list[str] = ["## Repository\n"]
    if ctx.get("remote_name"):
        lines.append(f"- **Project**: {ctx['remote_name']}")
    if ctx.get("branch"):
        lines.append(f"- **Branch**: `{ctx['branch']}`")
    if ctx.get("last_sha") and ctx.get("last_msg"):
        short_msg = ctx["last_msg"][:72]
        lines.append(f"- **Last commit**: `{ctx['last_sha']}` — {short_msg}")
    lines.append("")
    return "\n".join(lines) + "\n"


def _render_security_preamble(config: dict) -> str:
    """Build a Markdown security guidelines section from config."""
    sec = config.get("security", {})
    if not sec.get("enabled", False):
        return ""

    lines: list[str] = [
        "## Security Guidelines\n",
        "> The following rules apply to **all AI-generated code** in this repository.\n",
    ]

    lines.append(
        "- **Never generate**: hardcoded credentials, disabled SSL/TLS verification, "
        "permissive CORS (`*`), `eval()` on user input, or shell-injection patterns."
    )

    if sec.get("owasp_top10", True):
        owasp = ", ".join(_OWASP_ITEMS)
        lines.append(
            f"- **Follow OWASP Top 10** — prevent: {owasp}."
        )

    policy = sec.get("security_policy", "SECURITY.md")
    if policy:
        lines.append(
            f"- Review [`{policy}`]({policy}) for this project's vulnerability "
            "disclosure policy before suggesting security-related changes."
        )

    if sec.get("codeowners", True):
        lines.append(
            "- Sensitive paths are governed by [`CODEOWNERS`](CODEOWNERS) — "
            "changes to those files require additional human review."
        )

    for rule in sec.get("custom_rules", []):
        if rule.strip():
            lines.append(f"- {rule.strip()}")

    lines.append("")
    return "\n".join(lines) + "\n"


# Maps export target name → tag(s) an instructions spell must carry to appear in that file.
# A spell tagged 'all' appears in every lean file.
_TOOL_TAGS: dict[str, str] = {
    "agents":   "agents",
    "copilot":  "copilot",
    "cursor":   "cursor",
    "claude":   "claude",
    "windsurf": "windsurf",
    "gemini":   "gemini",
    "cline":    "cline",
    "aider":    "aider",
}


def _render_instructions(root: Path, tool: str) -> str:
    """Return Markdown for instructions spells scoped to *tool* (or tagged 'all')."""
    memories = list_memories(root, "instructions")
    if not memories:
        return ""

    tool_tag = _TOOL_TAGS.get(tool, tool)
    relevant = [
        m for m in memories
        if "all" in m.get("tags", []) or tool_tag in m.get("tags", [])
    ]
    if not relevant:
        return ""

    lines = ["## Instructions\n"]
    for m in relevant:
        lines.append(f"- {m['content']}")
    lines.append("")
    return "\n".join(lines) + "\n"


def _render_lean_body(config: dict, root: Path, tool: str = "") -> str:
    """Shared content for all lean AI instruction files.

    Contains: project description, tool-specific instructions, /lore hint,
    repo context, security preamble.
    Full memories live in CHRONICLE.md — the fat memory file.
    """
    body = ""

    desc = config.get("project_description", "").strip()
    if desc:
        body += f"{desc}\n\n"

    if tool:
        instructions = _render_instructions(root, tool)
        if instructions:
            body += instructions + "\n"

    body += (
        "**CHRONICLE.md** is the shared project memory file in this repository.  \n"
        "It is the canonical source of reviewed decisions, facts, preferences, and summaries for all AI tools.  \n"
        "For full context, read **CHRONICLE.md** before making non-trivial code changes.  \n"
        "When the user types `/lore`, read CHRONICLE.md before responding.\n"
    )

    repo = _render_repo_context(root)
    if repo:
        body += f"\n{repo}"

    security = _render_security_preamble(config)
    if security:
        body += f"\n{security}"

    return body


def _render_memories_markdown(root: Path) -> str:
    """Return all memories formatted as grouped Markdown for CHRONICLE.md.

    Instructions are rendered first (without tag decoration) so they read as
    clean directives, followed by all other tomes in alphabetical order.
    """
    memories = list_memories(root)
    if not memories:
        return "_No memories stored yet._\n"

    config = load_config(root)
    trust_cfg = config.get("trust", {})
    min_score = int(trust_cfg.get("chronicle_min_score", 0) or 0)
    default_score = int(trust_cfg.get("default_score", 50) or 50)

    filtered, skipped_low_trust = _filter_by_trust(memories, min_score, default_score)

    if not filtered:
        return "_No memories matched the current trust threshold for export._\n"

    by_cat = _group_memories_by_category(filtered)

    lines: list[str] = []
    if min_score > 0:
        lines.append(
            f"_Trust filter: exporting entries with trust score >= {min_score}. "
            f"Skipped {skipped_low_trust} low-trust entr{'y' if skipped_low_trust == 1 else 'ies'}._\n"
        )

    _append_instruction_section(lines, by_cat, default_score)
    _append_category_sections(lines, by_cat, default_score)
    return "\n".join(lines)


def _filter_by_trust(
    memories: list[dict[str, Any]],
    min_score: int,
    default_score: int,
) -> tuple[list[dict[str, Any]], int]:
    from .trust import memory_trust_score

    filtered: list[dict[str, Any]] = []
    skipped = 0
    for entry in memories:
        score = memory_trust_score(entry, default_score=default_score)
        if score < min_score:
            skipped += 1
            continue
        filtered.append(entry)
    return filtered, skipped


def _group_memories_by_category(memories: list[dict[str, Any]]) -> dict[str, list[dict[str, Any]]]:
    by_cat: dict[str, list[dict[str, Any]]] = defaultdict(list)
    for memory in memories:
        by_cat[memory.get("category", "facts")].append(memory)
    return by_cat


def _format_memory_line(
    entry: dict[str, Any],
    default_score: int,
    scoped_instructions: bool = False,
) -> str:
    from .trust import memory_trust_score, trust_level

    content = entry.get("content", "")
    score = memory_trust_score(entry, default_score=default_score)
    trust_part = f" _(trust: {trust_level(score)} {score})_"

    if scoped_instructions:
        tags = [t for t in entry.get("tags", []) if t not in ("all",)]
        scope = f" _(scope: {', '.join(tags)})_" if tags else " _(scope: all)_"
        return f"- {content}{scope}{trust_part}"

    tags = entry.get("tags", [])
    tag_str = f" _{', '.join(tags)}_" if tags else ""
    return f"- {content}{tag_str}{trust_part}"


def _append_instruction_section(
    lines: list[str],
    by_cat: dict[str, list[dict[str, Any]]],
    default_score: int,
) -> None:
    if "instructions" not in by_cat:
        return

    lines.append("## Instructions\n")
    for entry in by_cat["instructions"]:
        lines.append(_format_memory_line(entry, default_score, scoped_instructions=True))
    lines.append("")


def _append_category_sections(
    lines: list[str],
    by_cat: dict[str, list[dict[str, Any]]],
    default_score: int,
) -> None:
    for category in sorted(by_cat):
        if category == "instructions":
            continue
        lines.append(f"## {category.capitalize()}\n")
        for entry in by_cat[category]:
            lines.append(_format_memory_line(entry, default_score))
        lines.append("")


def _datestamp() -> str:
    return datetime.now(timezone.utc).strftime("%Y-%m-%d")


def _atomic_write(dest: Path, content: str) -> None:
    """Write *content* to *dest* atomically via a temp file + rename.

    Prevents partial/stale output if the process is interrupted mid-export.
    """
    dest.parent.mkdir(parents=True, exist_ok=True)
    fd, tmp = tempfile.mkstemp(dir=dest.parent, suffix=".tmp")
    try:
        with os.fdopen(fd, "w") as f:
            f.write(content)
        os.replace(tmp, dest)
    except Exception:
        try:
            os.unlink(tmp)
        except OSError:
            pass
        raise


def export_chronicle(root: Path, out: Path | None = None) -> Path:
    """Write CHRONICLE.md — the full project memory file referenced by lean instruction files."""
    body = _render_memories_markdown(root)
    dest = out or (root / "CHRONICLE.md")
    _atomic_write(
        dest,
        "# Chronicle\n\n"
        "> Auto-generated by `lore export`. Full project memory — "
        "load this file when you need deep context.\n\n"
        + _HEADER.format(date=_datestamp())
        + f"\n{body}"
    )
    return dest


def export_agents_md(root: Path, out: Path | None = None) -> Path:
    """Write lean AGENTS.md — references CHRONICLE.md for full memory."""
    config = load_config(root)
    dest = out or (root / "AGENTS.md")
    _atomic_write(
        dest,
        "# Agent Instructions\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="agents")
    )
    return dest


def export_copilot(root: Path, out: Path | None = None) -> Path:
    """Write lean .github/copilot-instructions.md — references CHRONICLE.md."""
    config = load_config(root)
    dest = out or (root / ".github" / "copilot-instructions.md")
    _atomic_write(
        dest,
        "<!-- Auto-generated by `lore export`. Do not edit manually. -->\n\n"
        "# Copilot Instructions\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="copilot")
    )
    return dest


def export_cursor(root: Path, out: Path | None = None) -> Path:
    """Write lean .cursor/rules/memory.md — references CHRONICLE.md at project root."""
    config = load_config(root)
    dest = out or (root / ".cursor" / "rules" / "memory.md")
    _atomic_write(
        dest,
        "# Project Instructions\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="cursor")
    )
    return dest


def export_claude(root: Path, out: Path | None = None) -> Path:
    """Write lean CLAUDE.md — references CHRONICLE.md for full memory."""
    config = load_config(root)
    dest = out or (root / "CLAUDE.md")
    _atomic_write(
        dest,
        "# Claude Instructions\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="claude")
    )
    return dest


def export_windsurf(root: Path, out: Path | None = None) -> Path:
    """Write lean .windsurfrules for Windsurf (Codeium) — opt-in target."""
    config = load_config(root)
    dest = out or (root / ".windsurfrules")
    _atomic_write(
        dest,
        "# Windsurf Rules\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="windsurf")
    )
    return dest


def export_gemini(root: Path, out: Path | None = None) -> Path:
    """Write lean GEMINI.md for Gemini CLI — opt-in target."""
    config = load_config(root)
    dest = out or (root / "GEMINI.md")
    _atomic_write(
        dest,
        "# Gemini Instructions\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="gemini")
    )
    return dest


def export_cline(root: Path, out: Path | None = None) -> Path:
    """Write lean .clinerules for Cline (VS Code AI agent) — opt-in target."""
    config = load_config(root)
    dest = out or (root / ".clinerules")
    _atomic_write(
        dest,
        "# Cline Rules\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="cline")
    )
    return dest


def export_aider(root: Path, out: Path | None = None) -> Path:
    """Write lean CONVENTIONS.md for Aider — opt-in target."""
    config = load_config(root)
    dest = out or (root / "CONVENTIONS.md")
    _atomic_write(
        dest,
        "# Conventions\n\n"
        + _HEADER.format(date=_datestamp())
        + "\n"
        + _render_lean_body(config, root, tool="aider")
    )
    return dest


def export_prompt(root: Path, out: Path | None = None) -> Path:
    """Write .github/prompts/lore.prompt.md — invokable as /lore in Copilot Chat."""
    config = load_config(root)
    desc = config.get("project_description", "").strip()
    desc_line = f"This project: {desc}\n\n" if desc else ""
    dest = out or (root / ".github" / "prompts" / "lore.prompt.md")
    _atomic_write(
        dest,
        "---\n"
        "mode: 'ask'\n"
        "description: 'Load full project memory from CHRONICLE.md'\n"
        "---\n\n"
        f"{desc_line}"
        "Read **CHRONICLE.md** in the project root (the canonical shared project memory) "
        "and summarize the context "
        "most relevant to the current task. Focus on decisions, preferences, "
        "and facts that affect how code should be written in this project.\n"
    )
    return dest


EXPORT_TARGETS: dict[str, Any] = {
    "chronicle": export_chronicle,  # full memory — written first; lean files reference it
    "agents":    export_agents_md,
    "copilot":   export_copilot,
    "cursor":    export_cursor,
    "claude":    export_claude,
    "windsurf":  export_windsurf,   # opt-in; set windsurf: true in .lore/config.yaml
    "gemini":    export_gemini,     # opt-in; set gemini: true in .lore/config.yaml
    "cline":     export_cline,      # opt-in; set cline: true in .lore/config.yaml
    "aider":     export_aider,      # opt-in; set aider: true in .lore/config.yaml
    "prompt":    export_prompt,     # /lore prompt file for Copilot Chat
}


def export_all(root: Path) -> list[Path]:
    """Export to all targets enabled in .memory/config.yaml."""
    config = load_config(root)
    enabled = config.get("export_targets", {})
    written: list[Path] = []
    for name, fn in EXPORT_TARGETS.items():
        if enabled.get(name, True):
            written.append(fn(root))
    return written
