"""Hermes Agent integration — skills-based agent.

Hermes Agent (https://github.com/NousResearch/hermes-agent) is an open-source
AI agent framework by Nous Research.  It stores skills in
``~/.hermes/skills/`` (user-global) rather than a project-local directory.

Usage::

    specify init my-project --integration hermes
    specify init --here --integration hermes
"""

from __future__ import annotations

from pathlib import Path
from shutil import rmtree
from typing import Any

import yaml

from ..base import IntegrationOption, SkillsIntegration, yaml_quote
from ..manifest import IntegrationManifest


class HermesIntegration(SkillsIntegration):
    """Integration for Hermes Agent skills.

    Hermes loads skills from ``~/.hermes/skills/`` (user home directory)
    rather than a project-local path.  Skills are installed directly to
    the global directory — no project-local copies are created since
    Hermes discovers them globally.  A project-local marker directory
    (``.hermes/skills/`` empty) is created so extension commands (e.g.
    git) can detect Hermes as an active integration.  Uninstall removes
    both the marker and all global ``speckit-*`` skills, matching the
    standard integration teardown behaviour.
    """

    key = "hermes"
    config = {
        "name": "Hermes Agent",
        "folder": ".hermes/",
        "commands_subdir": "skills",
        "install_url": "https://github.com/NousResearch/hermes-agent",
        "requires_cli": True,
    }
    registrar_config = {
        "dir": "~/.hermes/skills",
        "detect_dir": ".hermes/skills",
        "format": "markdown",
        "args": "$ARGUMENTS",
        "extension": "/SKILL.md",
    }

    # -- Helpers -----------------------------------------------------------

    @staticmethod
    def _hermes_home_skills_dir() -> Path:
        """Return ``~/.hermes/skills/`` — the global skills directory."""
        return Path.home() / ".hermes" / "skills"

    # -- Options -----------------------------------------------------------

    @classmethod
    def options(cls) -> list[IntegrationOption]:
        return [
            IntegrationOption(
                "--skills",
                is_flag=True,
                default=True,
                help="Install as agent skills (default for Hermes Agent)",
            ),
        ]

    # -- Setup -------------------------------------------------------------

    def setup(
        self,
        project_root: Path,
        manifest: IntegrationManifest,
        parsed_options: dict[str, Any] | None = None,
        **opts: Any,
    ) -> list[Path]:
        """Install command templates as global Hermes skills.

        Writes each skill directly to
        ``~/.hermes/skills/speckit-<name>/SKILL.md`` where Hermes
        discovers them at runtime.  No project-local SKILL.md copies are
        created — the global directory is the single source of truth.
        A project-local marker (``.hermes/skills/`` empty) is created
        so extension commands (e.g. git) can detect Hermes as an active
        integration.
        """
        templates = self.list_command_templates()
        if not templates:
            return []

        # Safety check: verify manifest project_root matches (standard pattern)
        project_root_resolved = project_root.resolve()
        if manifest.project_root != project_root_resolved:
            raise ValueError(
                f"manifest.project_root ({manifest.project_root}) does not match "
                f"project_root ({project_root_resolved})"
            )

        script_type = opts.get("script_type", "sh")
        arg_placeholder = (
            self.registrar_config.get("args", "$ARGUMENTS")
            if self.registrar_config
            else "$ARGUMENTS"
        )

        global_skills_dir = self._hermes_home_skills_dir()
        global_skills_dir.mkdir(parents=True, exist_ok=True)

        created: list[Path] = []

        for src_file in templates:
            raw = src_file.read_text(encoding="utf-8")

            # Derive the skill name from the template stem
            command_name = src_file.stem  # e.g. "plan"
            skill_name = f"speckit-{command_name.replace('.', '-')}"

            # Parse frontmatter for description
            frontmatter: dict[str, Any] = {}
            if raw.startswith("---"):
                parts = raw.split("---", 2)
                if len(parts) >= 3:
                    try:
                        fm = yaml.safe_load(parts[1])
                        if isinstance(fm, dict):
                            frontmatter = fm
                    except yaml.YAMLError:
                        pass

            # Process body through the standard template pipeline
            processed_body = self.process_template(
                raw,
                self.key,
                script_type,
                arg_placeholder,
                invoke_separator=self.invoke_separator,
                project_root=project_root,
            )
            # Strip the processed frontmatter — we rebuild it for skills.
            if processed_body.startswith("---"):
                parts = processed_body.split("---", 2)
                if len(parts) >= 3:
                    processed_body = parts[2]

            # Select description
            description = frontmatter.get("description", "")
            if not description:
                description = f"Spec Kit: {command_name} workflow"

            # Build SKILL.md with manually formatted frontmatter. yaml_quote
            # escapes newlines and control characters that a plain quoted
            # f-string cannot carry.
            skill_content = (
                f"---\n"
                f"name: {yaml_quote(skill_name)}\n"
                f"description: {yaml_quote(description)}\n"
                f"compatibility: "
                f"{yaml_quote('Requires spec-kit project structure with .specify/ directory')}\n"
                f"metadata:\n"
                f"  author: {yaml_quote('github-spec-kit')}\n"
                f"  source: {yaml_quote('templates/commands/' + src_file.name)}\n"
                f"---\n"
                f"{processed_body}"
            )

            skill_content = self.post_process_skill_content(skill_content)

            # Write directly to global ~/.hermes/skills/speckit-<name>/SKILL.md
            skill_dir = global_skills_dir / skill_name
            skill_dir.mkdir(parents=True, exist_ok=True)
            skill_file = skill_dir / "SKILL.md"
            normalized = skill_content.replace("\r\n", "\n")
            skill_file.write_bytes(normalized.encode("utf-8"))
            created.append(skill_file)


        # Create project-local marker directory so extension commands
        # (e.g. git) can detect Hermes as an active integration.
        # Hermes itself ignores this directory — skills live globally.
        (project_root / ".hermes" / "skills").mkdir(parents=True, exist_ok=True)

        return created

    # -- Uninstall ---------------------------------------------------------

    def teardown(
        self,
        project_root: Path,
        manifest: IntegrationManifest,
        *,
        force: bool = False,
    ) -> tuple[list[Path], list[Path]]:
        """Uninstall integration files including global Hermes skills.

        Removes the project-local marker directory (if empty), delegates to
        ``manifest.uninstall()`` for project-local tracked files, and
        removes all ``speckit-*`` skills under ``~/.hermes/skills/``.

        Global skills are always removed on teardown — this matches the
        standard integration behaviour where all files created by the
        integration are removed on ``specify integration uninstall``.
        """

        # Delegate to manifest for project-local tracked files (scripts,
        # templates, context entries tracked in the manifest).
        removed, skipped = manifest.uninstall(project_root, force=force)

        # Remove project-local marker directory if empty
        local_skills_dir = project_root / ".hermes" / "skills"
        if local_skills_dir.is_dir() and not any(local_skills_dir.iterdir()):
            local_skills_dir.rmdir()
            hermes_dir = project_root / ".hermes"
            if hermes_dir.is_dir() and not any(hermes_dir.iterdir()):
                hermes_dir.rmdir()

        # Remove all global Hermes skills for speckit — these are always
        # removed on uninstall regardless of the force flag, matching the
        # standard behaviour where all integration files are cleaned up.
        global_skills_dir = self._hermes_home_skills_dir()
        if global_skills_dir.is_dir():
            for skill_dir in sorted(global_skills_dir.iterdir()):
                if skill_dir.is_dir() and skill_dir.name.startswith("speckit-"):
                    try:
                        rmtree(skill_dir)
                        removed.append(skill_dir)
                    except OSError:
                        skipped.append(skill_dir)

        return removed, skipped

    # -- CLI dispatch ------------------------------------------------------

    def build_exec_args(
        self,
        prompt: str,
        *,
        model: str | None = None,
        output_json: bool = True,
    ) -> list[str] | None:
        """Build Hermes CLI invocation for programmatic dispatch.

        Uses ``hermes chat -Q -q`` for one-shot queries in quiet mode,
        mapping slash-command invocations to the appropriate skill-based
        dispatch.
        """
        args = [self._resolve_executable(), "chat", "-Q"]

        # Operator-supplied SPECKIT_INTEGRATION_HERMES_EXTRA_ARGS go here —
        # after the base command but before Spec Kit's canonical -m/--json/-s/-q
        # flags — so they can't displace or clobber them (mirrors opencode).
        self._apply_extra_args_env_var(args)

        if model:
            args.extend(["-m", model])
        if output_json:
            args.append("--json")

        # If prompt starts with a slash command, pass it directly
        # so Hermes can dispatch to the appropriate skill.
        if prompt.startswith("/"):
            command, _, remainder = prompt[1:].partition(" ")
            if command:
                args.extend(["-s", command])
                if remainder:
                    args.extend(["-q", remainder])
            else:
                args.extend(["-q", prompt])
        else:
            args.extend(["-q", prompt])

        return args
