"""Unified validation MCP tool registration.

This will become the single entrypoint for validation in the new design:
- deterministic git scopes (pre_commit / pre_push)
- config-driven routing and execution via `.aida/*`
- command execution + reusable output processors
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING, Annotated, Literal

from pydantic import Field

from aida_mcp.output import shrink_tool_payload
from aida_mcp.server.app_instance import Context
from aida_mcp.server.commit_cli import validate_commit_message
from aida_mcp.tools.core.git_policy_config import load_git_policy
from aida_mcp.tools.validate.engine import NO_MATCHING_VALIDATION_ROUTES_TEXT, execute_plan
from aida_mcp.utils.git import (
    get_remote_default_branch_ref,
    get_tracking_upstream_ref,
    list_changed_files,
    list_changed_files_vs_upstream,
    list_commit_messages_vs_upstream,
    list_remotes,
    list_tracked_files_under,
)
from aida_mcp.utils.repo_readiness import check_repo_readiness
from aida_mcp.utils.workspace import get_workspace_root

if TYPE_CHECKING:
    from mcp.server.fastmcp import FastMCP


def _run_pre_push_commit_policy_checks(
    *,
    workspace_root: str,
    resolved_upstream: str,
    head_ref: str,
) -> dict[str, object]:
    policy = load_git_policy(Path(workspace_root)).commit
    commits = list_commit_messages_vs_upstream(Path(workspace_root), upstream_ref=resolved_upstream, head_ref=head_ref)
    violations: list[dict[str, object]] = []
    for sha, message in commits:
        errors = validate_commit_message(message, policy=policy)
        if errors:
            violations.append({"sha": sha, "errors": errors})
    return {
        "checked_commits": len(commits),
        "violations": violations,
        "success": not violations,
        "text": "Commit policy checks passed." if not violations else "Commit policy checks failed.",
    }


def register(mcp: FastMCP) -> None:
    """Register unified validation tool."""
    mcp.tool()(validate)


async def validate(
    scope: Annotated[
        Literal["pre_commit", "pre_push"],
        Field(
            description=(
                "Validation scope. Keep this deterministic and minimal:\n"
                "- pre_commit: validate local working tree changes (called before commit)\n"
                "- pre_push: validate changes not yet in upstream plus dirty working tree (called before push)"
            )
        ),
    ],
    focus_paths: Annotated[
        list[str] | None,
        Field(
            description=(
                "Optional target roots (repo-relative dirs/files), e.g. 'microservices/gen-ai'. "
                "When set, git change discovery is skipped and validation runs for tracked files under these roots "
                "(uses `git ls-files`). Intended for 'validate X' even when no files changed."
            )
        ),
    ] = None,
    dry_run: Annotated[
        bool,
        Field(description="If true, compute scope and plan but do not execute commands. Default: false."),
    ] = False,
    explain: Annotated[
        bool,
        Field(
            description=(
                "If true, include scope computation details, routing decisions, and planned commands in the result."
            )
        ),
    ] = False,
    upstream_ref: Annotated[
        str | None,
        Field(
            description=(
                "Optional upstream reference override for pre_push (e.g. '@{u}', 'origin/main'). "
                "If omitted, the tool resolves upstream deterministically."
            )
        ),
    ] = None,
    head_ref: Annotated[
        str,
        Field(
            description="Optional head reference override (default: HEAD). Primarily for testing/CI; clients should not set this."
        ),
    ] = "HEAD",
    *,
    ctx: Context,
) -> object:
    workspace_root = await get_workspace_root(ctx)

    readiness = check_repo_readiness(workspace_root=workspace_root)
    if not readiness.validate_ready:
        missing = []
        if not readiness.config_root_exists:
            missing.append(f"missing config dir: {readiness.config_root}")
        else:
            missing.extend(
                [f"missing file: {readiness.config_root}/{name}" for name in readiness.missing_validate_files]
            )
        missing_text = "\n".join(f"- {m}" for m in missing) if missing else "- (unknown)"
        return shrink_tool_payload(
            {
                "tool": "validate",
                "success": False,
                "text": (
                    "Repository is not initialized for AIDA validation.\n\n"
                    f"{missing_text}\n\n"
                    "Initialize this repo (recommended): `aida-mcp init`."
                ),
                "scope": scope,
                "workspace_root": str(workspace_root),
            }
        )

    resolved_upstream: str | None = None
    upstream_source: str | None = None

    if focus_paths:
        changed_paths = list_tracked_files_under(workspace_root, focus_paths)
        scope_source = "focus_paths"
    elif scope == "pre_commit":
        changed_paths = list_changed_files(workspace_root, include_untracked=True)
        scope_source = "git.pre_commit"
    else:
        resolved_upstream = upstream_ref or get_tracking_upstream_ref(workspace_root)
        upstream_source = "upstream_ref" if upstream_ref else "git.@{u}"

        if resolved_upstream is None:
            remotes = list_remotes(workspace_root)
            if len(remotes) == 1:
                resolved_upstream = get_remote_default_branch_ref(workspace_root, remotes[0])
                upstream_source = f"git.remote_head({remotes[0]})"

        if resolved_upstream is None:
            return {
                "tool": "validate",
                "success": False,
                "text": (
                    "pre_push requires an upstream. Set tracking upstream for this branch or pass upstream_ref. "
                    "Examples: upstream_ref='@{u}' or upstream_ref='origin/main'."
                ),
                "scope": scope,
                "workspace_root": str(workspace_root),
            }

        changed_vs_upstream = list_changed_files_vs_upstream(
            workspace_root, upstream_ref=resolved_upstream, head_ref=head_ref
        )
        dirty = list_changed_files(workspace_root, include_untracked=True)
        changed_paths = sorted({*changed_vs_upstream, *dirty})
        scope_source = "git.pre_push"

    payload = await execute_plan(
        workspace_root=workspace_root,
        scope=scope,
        changed_paths=changed_paths,
        dry_run=dry_run,
        explain=explain,
        ctx=ctx,
    )
    # Preserve scope metadata.
    payload.setdefault("scope_source", scope_source)
    payload.setdefault("workspace_root", str(workspace_root))
    payload.setdefault("focus_paths", focus_paths or [])
    payload.setdefault("dry_run", dry_run)
    payload.setdefault("explain", explain)
    if scope == "pre_push":
        payload.setdefault("resolved_upstream_ref", resolved_upstream)
        payload.setdefault("upstream_source", upstream_source)
        payload.setdefault("upstream_ref", upstream_ref)
        payload.setdefault("head_ref", head_ref)
        if resolved_upstream:
            commit_policy = _run_pre_push_commit_policy_checks(
                workspace_root=str(workspace_root),
                resolved_upstream=resolved_upstream,
                head_ref=head_ref,
            )
            payload["commit_policy"] = commit_policy
            if not commit_policy.get("success", True):
                payload["success"] = False
                existing_text = str(payload.get("text", "")).strip()
                payload["text"] = (
                    f"{existing_text}\n❌ pre_push commit policy checks failed"
                    if existing_text
                    else "❌ pre_push commit policy checks failed"
                )
    # For focus_paths we expect deterministic routing. If nothing matches, return an explicit error.
    if scope_source == "focus_paths" and payload.get("text") == NO_MATCHING_VALIDATION_ROUTES_TEXT:
        payload["success"] = False
        payload["text"] = (
            "No matching validation routes for focus_paths. "
            "Pass a file path or any directory under a configured component root."
        )
    return shrink_tool_payload(payload)
