"""Agent instruction file generation for gjalla attestation guidance."""

import re
from pathlib import Path


# Maps agent name → relative file path from repo root
AGENT_FILE_MAP: dict[str, str] = {
    "claude-code": "CLAUDE.md",
    "gemini-cli": "GEMINI.md",
    "cursor": ".cursor/rules/gjalla.mdc",
    "codex": "AGENTS.md",
}

_TAG_START = "<!-- gjalla-start -->"
_TAG_END = "<!-- gjalla-end -->"

# Legacy marker for backward-compatible replacement of pre-tag files
_LEGACY_SECTION_MARKER = "## Gjalla System-level Version Control"

_CORE_TEMPLATE = """\
## Gjalla System-level Version Control

This project uses gjalla to track system-level changes, such as architecture, tech stack, external dependencies, and rules/constraints. Project ID: $PROJECT_ID

### Setup
Ensure gjalla CLI is installed: `pip install gjalla` (or `pipx install gjalla`)
Ensure the gjalla pre-commit and post-commit hooks are installed by running `gjalla init`. Do NOT manually install hooks or use other hook frameworks (e.g. husky, pre-commit) — `gjalla init` detects the repo's existing hook setup and installs into the correct location automatically.

### Before Every Commit
1. Stage your changes
2. Compute diff hash: `git diff --staged | shasum -a 256 | cut -d' ' -f1`
3. Get project rules and architecture using **one of these** (in order of preference):
   - `gjalla show rules` and `gjalla show arch` (works in both local-only and remote modes)
   - Read `.gjalla/cache/rules.md` and `.gjalla/cache/architecture.txt` directly (remote mode only)
   - Fetch from the gjalla MCP server (`get_project_rules`, `get_architecture_spec`)
   If the cache is missing or stale, run `gjalla sync` to refresh it.
4. Write `.gjalla/.commit-attestation.yaml` using the format in `.gjalla/example-attestation.yaml` — fill in ALL primitives under the `changes:` key: architecture (elements, connections, decisions), data_model, data_flows, rules, capabilities, api_surface, external_dependencies, tech_stack
5. The pre-commit hook validates the diff hash. Retry `git commit` after writing the attestation.

### System-level Context
Use the gjalla CLI (`gjalla show rules`, `gjalla show arch`, `gjalla show state`) or the gjalla MCP server for rules, architecture specs, and other guidance before making changes. The CLI works in both local-only and remote modes — just call it and it will return the right data. Fall back to the MCP server if the CLI is unavailable.
Your attestation lets you confirm your adherence to the system's source of truth and report any meaningful changes to the system.

### Windows Note
On Windows, Git for Windows includes its own POSIX shell (MSYS2), so the shell commands above (e.g. `shasum`, `cut`) work inside git hooks and Git Bash. You do not need WSL or a separate Unix environment.
"""

_CURSOR_FRONTMATTER = """\
---
description: Gjalla architecture governance and attestation requirements
alwaysApply: true
---
"""

# Sanitize project_id: only allow alphanumeric, dash, underscore, dot
_PROJECT_ID_RE = re.compile(r"[^a-zA-Z0-9._-]")


def _sanitize_project_id(project_id: str) -> str:
    """Strip characters that could inject content into markdown templates."""
    return _PROJECT_ID_RE.sub("", project_id)


def _wrap_in_tags(content: str) -> str:
    """Wrap content in gjalla start/end tags."""
    return f"{_TAG_START}\n{content.rstrip()}\n{_TAG_END}\n"


def generate_agent_guidance(agent: str, project_id: str) -> str:
    """Return the full file content for the given agent type."""
    safe_id = _sanitize_project_id(project_id)
    body = _CORE_TEMPLATE.replace("$PROJECT_ID", safe_id)
    if agent == "cursor":
        return _CURSOR_FRONTMATTER + _wrap_in_tags(body)
    return _wrap_in_tags(body)


def detect_agents(repo_root: Path) -> list[str]:
    """Return agent names whose instruction files already exist."""
    found = []
    for agent, rel_path in AGENT_FILE_MAP.items():
        if (repo_root / rel_path).exists():
            found.append(agent)
    return found


def write_agent_guidance(repo_root: Path, agent: str, project_id: str) -> Path:
    """Write or update the agent guidance file. Returns the path written.

    Raises ValueError if agent is not in AGENT_FILE_MAP.
    """
    if agent not in AGENT_FILE_MAP:
        raise ValueError(f"Unknown agent {agent!r}. Valid: {', '.join(AGENT_FILE_MAP)}")

    rel_path = AGENT_FILE_MAP[agent]
    target = repo_root / rel_path

    content = generate_agent_guidance(agent, project_id)

    # Cursor: always write as standalone file (won't conflict with other rule files)
    if agent == "cursor":
        target.parent.mkdir(parents=True, exist_ok=True)
        target.write_text(content, encoding="utf-8")
        return target

    # For markdown files: append or replace section
    if target.exists():
        existing = target.read_text(encoding="utf-8")
        if _TAG_START in existing and _TAG_END in existing:
            # Replace between tags (inclusive)
            pattern = re.escape(_TAG_START) + r".*?" + re.escape(_TAG_END) + r"\n?"
            replaced = re.sub(pattern, content, existing, count=1, flags=re.DOTALL)
            if not replaced.endswith("\n"):
                replaced += "\n"
            target.write_text(replaced, encoding="utf-8")
        elif _LEGACY_SECTION_MARKER in existing:
            # Upgrade from legacy marker to tagged format
            pattern = re.escape(_LEGACY_SECTION_MARKER) + r".*?(?=\n## (?!Gjalla)|\Z)"
            replaced = re.sub(pattern, content.rstrip(), existing, count=1, flags=re.DOTALL)
            if not replaced.endswith("\n"):
                replaced += "\n"
            target.write_text(replaced, encoding="utf-8")
        else:
            # Append with separator
            separator = "\n" if existing.endswith("\n") else "\n\n"
            target.write_text(existing + separator + content, encoding="utf-8")
    else:
        target.write_text(content, encoding="utf-8")

    return target
