"""Shared instruction-artifact generation helpers.

This module centralizes generation logic for:
- AGENTS.md
- .cursorrules
- CLAUDE.md
"""

from __future__ import annotations

import subprocess
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, Optional, Tuple

import ontos
from ontos.core.instruction_protocol import (
    InstructionProtocolConfig,
    preserve_user_custom_section,
    render_activation_protocol_head,
    render_activation_protocol_tail,
    render_trigger_phrases,
)
from ontos.core.claude_template import generate_claude_content


@dataclass(frozen=True)
class InstructionArtifactResult:
    """Result payload for one generated artifact."""

    path: str
    created: bool
    message: str


AGENTS_PROTOCOL_CONFIG = InstructionProtocolConfig(
    instruction_file="AGENTS.md",
    regenerate_command="ontos agents",
    regenerate_purpose="Generate instruction files",
    staleness_command="ontos map --sync-agents",
    map_sync_command="ontos map --sync-agents",
    map_sync_purpose="Regenerate map + sync AGENTS.md",
)

AGENTS_HEADER_TEMPLATE = """# AGENTS.md

This project uses **Ontos** for documentation management.

Generated by Ontos v{ontos_version} on {generated_at}"""

AGENTS_CURRENT_PROJECT_STATE_TEMPLATE = """## Current Project State

> Auto-synced: {generated_at}

| Metric | Value |
|--------|-------|
| Branch | {current_branch} |
| Doc Count | {doc_count} |
| Last Log | {last_log} |
| Health | {health_summary} |"""

AGENTS_PROJECT_STATS_TEMPLATE = """## Project Stats
- Doc Count: {doc_count}
- Last Updated: {last_updated}"""


def find_repo_root() -> Optional[Path]:
    """Find the repository root using Ontos and git markers."""
    current = Path.cwd()

    for parent in [current] + list(current.parents):
        if (parent / ".ontos.toml").exists():
            return parent

    try:
        result = subprocess.run(
            ["git", "rev-parse", "--show-toplevel"],
            capture_output=True,
            text=True,
            timeout=5,
        )
        if result.returncode == 0:
            return Path(result.stdout.strip())
    except (subprocess.TimeoutExpired, FileNotFoundError):
        pass

    for parent in [current] + list(current.parents):
        if (parent / ".git").exists():
            return parent

    return None


def gather_stats(repo_root: Path, scope: Optional[str] = None) -> Dict[str, str]:
    """Gather project stats for instruction templates."""
    stats = {
        "doc_count": "Unknown",
        "last_updated": "Unknown",
        "current_branch": "Unknown",
        "last_log": "None",
        "health_summary": "Unknown",
    }

    try:
        config_path = repo_root / ".ontos.toml"
        docs_dir = repo_root / "docs"
        logs_dir = repo_root / "docs" / "logs"
        context_map = repo_root / "Ontos_Context_Map.md"

        skip_patterns = []
        doc_paths = []
        if config_path.exists():
            try:
                from ontos.io.config import load_project_config
                from ontos.io.scan_scope import collect_scoped_documents, resolve_scan_scope

                config = load_project_config(repo_root=repo_root)
                docs_dir = repo_root / config.paths.docs_dir
                logs_dir = repo_root / config.paths.logs_dir
                context_map = repo_root / config.paths.context_map
                skip_patterns = config.scanning.skip_patterns

                effective_scope = resolve_scan_scope(scope, config.scanning.default_scope)
                doc_paths = collect_scoped_documents(
                    repo_root,
                    config,
                    effective_scope,
                    base_skip_patterns=skip_patterns,
                )
            except Exception:
                doc_paths = []

        if not doc_paths and docs_dir.exists():
            from ontos.io.files import scan_documents

            doc_paths = scan_documents([docs_dir], skip_patterns=skip_patterns)

        count = len(doc_paths)
        stats["doc_count"] = "5000+" if count >= 5000 else str(count)

        mtimes = []
        for path in [context_map, config_path]:
            if path.exists():
                mtimes.append(path.stat().st_mtime)

        if logs_dir.exists():
            for log_file in logs_dir.glob("*.md"):
                mtimes.append(log_file.stat().st_mtime)

        if mtimes:
            max_mtime = max(mtimes)
            dt = datetime.fromtimestamp(max_mtime, tz=timezone.utc)
            stats["last_updated"] = dt.strftime("%Y-%m-%d %H:%M:%S UTC")

        try:
            result = subprocess.run(
                ["git", "rev-parse", "--abbrev-ref", "HEAD"],
                capture_output=True,
                text=True,
                timeout=5,
                cwd=repo_root,
            )
            if result.returncode == 0:
                stats["current_branch"] = result.stdout.strip()
        except (subprocess.TimeoutExpired, FileNotFoundError):
            pass

        if logs_dir.exists():
            log_files = sorted(logs_dir.glob("*.md"), reverse=True)
            if log_files:
                stats["last_log"] = log_files[0].stem

        stats["health_summary"] = "✓ Map exists" if context_map.exists() else "⚠ No map"
    except Exception:
        pass

    return stats


def generate_agents_content(existing_content: Optional[str] = None, scope: Optional[str] = None) -> str:
    """Generate AGENTS.md content with project stats."""
    repo_root = find_repo_root()
    stats = gather_stats(repo_root, scope=scope) if repo_root else gather_stats(Path.cwd(), scope=scope)

    generated_at = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S UTC")

    header = AGENTS_HEADER_TEMPLATE.format(
        ontos_version=ontos.__version__,
        generated_at=generated_at,
    )

    current_project_state = AGENTS_CURRENT_PROJECT_STATE_TEMPLATE.format(
        generated_at=generated_at,
        current_branch=stats["current_branch"],
        doc_count=stats["doc_count"],
        last_log=stats["last_log"],
        health_summary=stats["health_summary"],
    )

    trigger_phrases = render_trigger_phrases()
    activation_head = render_activation_protocol_head(AGENTS_PROTOCOL_CONFIG)
    project_stats = AGENTS_PROJECT_STATS_TEMPLATE.format(
        doc_count=stats["doc_count"],
        last_updated=stats["last_updated"],
    )
    activation_tail = render_activation_protocol_tail(AGENTS_PROTOCOL_CONFIG)

    content = "\n\n".join(
        [
            header,
            trigger_phrases,
            current_project_state,
            activation_head,
            project_stats,
            activation_tail,
        ]
    ) + "\n"

    return preserve_user_custom_section(content, existing_content)


def transform_to_cursorrules(agents_content: str) -> str:
    """Transform AGENTS.md content to .cursorrules format."""
    lines = agents_content.split("\n")
    result = []
    skip_until_next_section = False

    for line in lines:
        if line.startswith("# AGENTS.md"):
            result.append("# Ontos Protocol for Cursor")
            continue

        if line.startswith("## "):
            section_name = line[3:].strip()
            skip_until_next_section = False

            if section_name == "Ontos Activation":
                result.append("")
                result.append('When the user says "Ontos" or "Activate Ontos":')
                result.append("")
                result.append("## Activation")
                continue
            if section_name == "Session End":
                result.append("")
                result.append("When ending a session:")
                result.append("")
                result.append("## Session End")
                continue
            if section_name in (
                "Trigger Phrases",
                "Re-Activation Trigger",
                "After Context Compaction (/compact)",
                "Quick Reference",
                "Project Stats",
                "Core Invariants",
                "Staleness",
            ):
                result.append(line)
                continue

            skip_until_next_section = True
            continue

        if skip_until_next_section:
            continue
        result.append(line)

    return "\n".join(result)


def create_backup(path: Path) -> None:
    """Create a `.bak` backup if file exists."""
    if path.exists():
        backup_path = path.with_suffix(path.suffix + ".bak")
        backup_path.write_text(path.read_text(encoding="utf-8"), encoding="utf-8")


def generate_agents_files(
    *,
    repo_root: Path,
    output_path: Optional[Path] = None,
    force: bool = False,
    format: str = "agents",
    all_formats: bool = False,
    scope: Optional[str] = None,
) -> Tuple[int, str]:
    """Generate AGENTS.md and/or .cursorrules."""
    messages = []
    generate_agents = format == "agents" or all_formats
    generate_cursor = format == "cursor" or all_formats

    if generate_agents:
        agents_path = output_path or repo_root / "AGENTS.md"
        try:
            agents_path.resolve().relative_to(repo_root.resolve())
        except ValueError:
            return 2, f"Error: Output path must be within repository root ({repo_root})"

        if agents_path.exists() and not force:
            return 1, f"AGENTS.md already exists at {agents_path}. Use --force to overwrite."

        try:
            if force and agents_path.exists():
                create_backup(agents_path)
            existing_content = agents_path.read_text(encoding="utf-8") if agents_path.exists() else None
            agents_path.parent.mkdir(parents=True, exist_ok=True)
            agents_path.write_text(
                generate_agents_content(existing_content, scope=scope),
                encoding="utf-8",
            )
            messages.append(f"Created {agents_path}")
        except Exception as exc:
            return 2, f"Error writing AGENTS.md: {exc}"

    if generate_cursor:
        cursor_path = repo_root / ".cursorrules"
        if cursor_path.exists() and not force:
            if generate_agents:
                messages.append(".cursorrules already exists. Use --force to overwrite.")
            else:
                return 1, f".cursorrules already exists at {cursor_path}. Use --force to overwrite."
        else:
            try:
                if force and cursor_path.exists():
                    create_backup(cursor_path)
                existing_agents_path = repo_root / "AGENTS.md"
                existing_content = (
                    existing_agents_path.read_text(encoding="utf-8")
                    if existing_agents_path.exists()
                    else None
                )
                agents_content = generate_agents_content(existing_content, scope=scope)
                cursor_content = transform_to_cursorrules(agents_content)
                cursor_path.write_text(cursor_content, encoding="utf-8")
                messages.append(f"Created {cursor_path}")
            except Exception as exc:
                return 2, f"Error writing .cursorrules: {exc}"

    return 0, "\n".join(messages) if messages else "No files generated"


def generate_claude_file(
    *,
    repo_root: Path,
    output_path: Optional[Path] = None,
    force: bool = False,
) -> Tuple[int, str]:
    """Generate CLAUDE.md."""
    path = output_path or repo_root / "CLAUDE.md"

    try:
        resolved_output = path.resolve()
        resolved_root = repo_root.resolve()
        if (repo_root / ".git").exists() or (repo_root / ".ontos.toml").exists():
            resolved_output.relative_to(resolved_root)
    except ValueError:
        return 2, f"Error: Output path must be within repository root ({repo_root})"

    if path.exists() and not force:
        return 1, f"CLAUDE.md already exists at {path}. Use --force to overwrite."

    try:
        existing_content = path.read_text(encoding="utf-8") if path.exists() else None
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_text(generate_claude_content(existing_content), encoding="utf-8")
    except (IOError, OSError) as exc:
        return 2, f"Error writing file to {path}: {exc}"
    except Exception as exc:
        return 2, f"An unexpected error occurred: {exc}"

    return 0, f"Created {path}"


def generate_all_instruction_exports(
    *,
    repo_root: Path,
    force: bool = False,
    scope: Optional[str] = None,
) -> Tuple[int, Dict[str, InstructionArtifactResult]]:
    """Generate AGENTS.md, .cursorrules, and CLAUDE.md in one operation."""
    results: Dict[str, InstructionArtifactResult] = {}

    agents_code, agents_message = generate_agents_files(
        repo_root=repo_root,
        force=force,
        all_formats=True,
        scope=scope,
    )
    results["agents_bundle"] = InstructionArtifactResult(
        path=str(repo_root),
        created=agents_code == 0,
        message=agents_message,
    )

    claude_code, claude_message = generate_claude_file(
        repo_root=repo_root,
        force=force,
    )
    results["claude"] = InstructionArtifactResult(
        path=str(repo_root / "CLAUDE.md"),
        created=claude_code == 0,
        message=claude_message,
    )

    exit_code = 0 if agents_code == 0 and claude_code == 0 else 1
    return exit_code, results
