"""Canonical workflow skill naming and preflight for multi-runtime agents (#551).

Controller prompts and Claude historically require ``gitea-workflow``. The
portable in-repo skill is ``skills/llm-project-workflow``. Codex must resolve
the same canonical names so sessions do not proceed without the workflow wall.
"""

from __future__ import annotations

import os
from pathlib import Path
from typing import Any

# Canonical names that all runtimes (Claude, Codex, Gemini, MCP inventory)
# must treat as the same workflow router skill.
CANONICAL_WORKFLOW_SKILL_NAMES = (
    "gitea-workflow",
    "llm-project-workflow",
    "git-pr-workflows",
)

# Primary checked-in skill path (portable source of truth).
REPO_SKILL_REL_PATH = "skills/llm-project-workflow/SKILL.md"
# In-repo alias directory so scanners that look for skills/gitea-workflow work.
REPO_ALIAS_SKILL_REL_PATH = "skills/gitea-workflow/SKILL.md"

CODEX_SKILLS_ENV = "CODEX_HOME"
DEFAULT_CODEX_SKILLS = os.path.expanduser("~/.codex/skills")


def default_codex_skills_dir() -> str:
    home = (os.environ.get(CODEX_SKILLS_ENV) or "").strip()
    if home:
        return os.path.join(os.path.expanduser(home), "skills")
    return DEFAULT_CODEX_SKILLS


def resolve_repo_skill_path(project_root: str) -> Path:
    return Path(project_root) / REPO_SKILL_REL_PATH


def resolve_repo_alias_skill_path(project_root: str) -> Path:
    return Path(project_root) / REPO_ALIAS_SKILL_REL_PATH


def normalize_skill_name(name: str | None) -> str:
    return (name or "").strip().lower().replace("_", "-")


def is_canonical_workflow_skill_name(name: str | None) -> bool:
    return normalize_skill_name(name) in CANONICAL_WORKFLOW_SKILL_NAMES


def assess_workflow_skill_presence(
    project_root: str,
    *,
    codex_skills_dir: str | None = None,
) -> dict[str, Any]:
    """Return presence proof for the canonical workflow skill.

    Fail-closed when the in-repo skill file is missing. Codex mount is advisory
    in the report (operators install via script) but ``codex_skill_mounted`` is
    explicit so preflight can BLOCK when required.
    """
    root = (project_root or "").strip()
    reasons: list[str] = []
    repo_path = resolve_repo_skill_path(root) if root else None
    alias_path = resolve_repo_alias_skill_path(root) if root else None
    repo_present = bool(repo_path and repo_path.is_file())
    alias_present = bool(alias_path and alias_path.is_file())

    if not root:
        reasons.append("project_root not provided (fail closed)")
    elif not repo_present:
        reasons.append(
            f"canonical workflow skill missing at {REPO_SKILL_REL_PATH} "
            "(fail closed — do not mutate without workflow wall)"
        )

    codex_dir = Path(codex_skills_dir or default_codex_skills_dir())
    codex_mounts: dict[str, bool] = {}
    for name in CANONICAL_WORKFLOW_SKILL_NAMES:
        skill_md = codex_dir / name / "SKILL.md"
        codex_mounts[name] = skill_md.is_file()
    codex_any = any(codex_mounts.values())

    blocked = bool(reasons)
    return {
        "canonical_names": list(CANONICAL_WORKFLOW_SKILL_NAMES),
        "primary_name": "gitea-workflow",
        "portable_name": "llm-project-workflow",
        "repo_skill_rel_path": REPO_SKILL_REL_PATH,
        "repo_skill_present": repo_present,
        "repo_alias_rel_path": REPO_ALIAS_SKILL_REL_PATH,
        "repo_alias_present": alias_present,
        "codex_skills_dir": str(codex_dir),
        "codex_skill_mounts": codex_mounts,
        "codex_skill_mounted": codex_any,
        "workflow_skill_ready": repo_present,
        "blocked": blocked,
        "block_reason": reasons[0] if reasons else None,
        "reasons": reasons,
        "safe_next_action": (
            "Install or repair skills/llm-project-workflow/SKILL.md in the "
            "repo; run scripts/install-codex-workflow-skill.sh for Codex; "
            "call mcp_list_project_skills and load gitea-workflow / "
            "llm-project-workflow before any git/Gitea mutation."
            if blocked or not codex_any
            else "Load gitea-workflow or llm-project-workflow, then proceed."
        ),
    }


def workflow_skill_registry_entries() -> dict[str, dict[str, Any]]:
    """Shared skill metadata for mcp_list_project_skills / guides."""
    shared_steps = [
        "Call mcp_list_project_skills and confirm gitea-workflow (or "
        "llm-project-workflow) is listed.",
        "Call mcp_check_workflow_skill_preflight and stop if blocked.",
        "Load skills/llm-project-workflow/SKILL.md (or the gitea-workflow "
        "alias) and route to the matching workflow file for the task mode.",
        "Do not perform git or Gitea mutations until the workflow is loaded.",
        "Codex: ensure ~/.codex/skills/gitea-workflow -> repo skill via "
        "scripts/install-codex-workflow-skill.sh.",
    ]
    notes = (
        f"Canonical names: {', '.join(CANONICAL_WORKFLOW_SKILL_NAMES)}. "
        f"Portable source: {REPO_SKILL_REL_PATH}. "
        "Controller prompts may say gitea-workflow; Codex and MCP inventory "
        "must resolve the same skill."
    )
    base = {
        "description": (
            "Canonical Gitea/LLM project workflow router: pick task mode, "
            "load the matching workflow, enforce isolation, emit the final "
            "report schema."
        ),
        "when_to_use": (
            "At the start of every author, review, merge, reconcile, or "
            "issue-filing session — before any mutation."
        ),
        "required_operations": ["gitea.read"],
        "status": "available",
        "notes": notes,
        "steps": shared_steps,
        "repo_path": REPO_SKILL_REL_PATH,
        "canonical": True,
    }
    return {
        "gitea-workflow": dict(base),
        "llm-project-workflow": dict(base),
        "git-pr-workflows": {
            **dict(base),
            "description": (
                "Legacy alias for the canonical Gitea/LLM project workflow "
                "router (same as gitea-workflow / llm-project-workflow)."
            ),
            "notes": notes + " Prefer gitea-workflow in new prompts.",
        },
    }
