"""Create a structured commit attestation."""

import json
import subprocess
from datetime import datetime, timezone

import click
import yaml

from ..display.output import console, success, error, info
from ._shared import get_repo_root, get_staged_diff_hash, get_git_user_name

ARCH_PRIMITIVES = [
    "architecture_elements",
    "architecture_connections",
    "architecture_decisions",
    "data_flows",
    "tech_stack",
    "external_dependencies",
    "services",
    "capabilities",
]

# Grouped choices for the interactive prompt. Each entry maps a display label
# to the underlying YAML keys it covers.
INTERACTIVE_GROUPS = [
    ("Architecture (elements, connections, decisions)", [
        "architecture_elements",
        "architecture_connections",
        "architecture_decisions",
    ]),
    ("Data flows", ["data_flows"]),
    ("Tech stack", ["tech_stack"]),
    ("External dependencies & services", ["external_dependencies", "services"]),
    ("Capabilities", ["capabilities"]),
]


def _has_staged_changes() -> bool:
    """Check if there are staged changes."""
    result = subprocess.run(
        ["git", "diff", "--staged", "--quiet"],
        capture_output=True,
    )
    return result.returncode != 0


def _interactive_attest() -> None:
    """Run interactive attestation flow for humans."""
    repo_root = get_repo_root()

    # Check staged changes exist
    if not _has_staged_changes():
        error("No staged changes. Stage your changes with 'git add' first.")
        raise SystemExit(1)

    # Auto-compute diff hash
    staged_diff_hash = get_staged_diff_hash()
    info(f"Staged diff hash: {staged_diff_hash[:16]}...")

    # Get git identity
    git_name = get_git_user_name()
    info(f"Attesting as: {git_name}")

    # Prompt: impact summary
    console.print()
    summary = click.prompt(
        click.style("What's the impact of this change?", fg="cyan"),
        type=str,
    )

    # Prompt: architecture areas (multi-select via numbered list)
    console.print()
    console.print("[cyan]Which architecture areas changed?[/cyan]")
    for i, (label, _keys) in enumerate(INTERACTIVE_GROUPS, 1):
        console.print(f"  {i}. {label}")
    console.print(f"  0. None")
    console.print()
    selection = click.prompt(
        "Enter numbers separated by commas, or 0 for none",
        type=str,
        default="0",
    )

    # Parse selection → set of selected primitive keys
    selected_primitives: set[str] = set()
    if selection.strip() != "0":
        for part in selection.split(","):
            part = part.strip()
            if part.isdigit() and 1 <= int(part) <= len(INTERACTIVE_GROUPS):
                _label, keys = INTERACTIVE_GROUPS[int(part) - 1]
                selected_primitives.update(keys)

    # Prompt for details on each selected group
    arch_data: dict[str, dict] = {}
    prompted_keys: set[str] = set()
    for label, keys in INTERACTIVE_GROUPS:
        if any(k in selected_primitives for k in keys):
            console.print()
            details = click.prompt(
                click.style(f"Describe the {label.lower()} changes", fg="cyan"),
                type=str,
            )
            for k in keys:
                arch_data[k] = {"changed": True, "details": [details]}
                prompted_keys.add(k)

    # Fill in unchanged primitives
    for prim in ARCH_PRIMITIVES:
        if prim not in prompted_keys:
            arch_data[prim] = {"changed": False}

    # Build attestation
    timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
    attestation = {
        "staged_diff_hash": staged_diff_hash,
        "timestamp": timestamp,
        "agent": git_name,
        "agent_provider": "human",
        "agent_model": "n/a",
        "rules": {"checked": False},
    }

    for key in ARCH_PRIMITIVES:
        attestation[key] = arch_data[key]

    attestation["summary"] = summary

    # Write attestation
    gjalla_dir = repo_root / ".gjalla"
    gjalla_dir.mkdir(parents=True, exist_ok=True)
    attestation_path = gjalla_dir / ".commit-attestation.yaml"
    yaml_body = yaml.dump(attestation, default_flow_style=False, sort_keys=False)
    attestation_path.write_text(f"---\n{yaml_body}---\n", encoding="utf-8")

    console.print()
    success("Created .gjalla/.commit-attestation.yaml")
    info("  Run 'git commit' — the pre-commit hook will verify the hash.")


@click.command()
@click.option("--staged-diff-hash", default=None, help="SHA-256 of staged diff.")
@click.option("--agent", default=None, help="Agent name (e.g. claude-code, cursor, aider).")
@click.option("--provider", default=None, help="Model provider (e.g. anthropic, openai).")
@click.option("--model", default=None, help="Model ID (e.g. claude-opus-4-6).")
@click.option("--summary", default=None, help="One-line impact summary.")
@click.option("--agent-id", default=None, help="Optional agent identifier (e.g. session ID).")
@click.option("--rules", default=None, help="JSON string: {\"checked\": true, \"applicable\": [...]}.")
@click.option("--arch-changes", default=None, help="JSON string with architecture change primitives.")
@click.option("--timestamp", default=None, help="ISO 8601 timestamp. Auto-generated if omitted.")
def attest(
    staged_diff_hash: str | None,
    agent: str | None,
    provider: str | None,
    model: str | None,
    summary: str | None,
    agent_id: str | None,
    rules: str | None,
    arch_changes: str | None,
    timestamp: str | None,
) -> None:
    """Create .gjalla/.commit-attestation.yaml from structured flags.

    \b
    Run with no flags for interactive mode (humans), or pass all
    required flags for programmatic use (agents/scripts).

    \b
    Interactive:
        gjalla attest

    \b
    Programmatic:
        gjalla attest \\
          --staged-diff-hash $(git diff --staged | shasum -a 256 | cut -d' ' -f1) \\
          --agent claude-code --provider anthropic --model claude-opus-4-6 \\
          --summary "Added rate limiting to auth endpoints" \\
          --rules '{"checked": true, "applicable": [...]}' \\
          --arch-changes '{"architecture_elements": {"changed": true, ...}}'
    """
    required_flags = [staged_diff_hash, agent, provider, model, summary]
    has_any = any(f is not None for f in required_flags)
    has_all = all(f is not None for f in required_flags)

    # Partial flags → error
    if has_any and not has_all:
        missing = []
        if staged_diff_hash is None:
            missing.append("--staged-diff-hash")
        if agent is None:
            missing.append("--agent")
        if provider is None:
            missing.append("--provider")
        if model is None:
            missing.append("--model")
        if summary is None:
            missing.append("--summary")
        error(f"Missing required flags: {', '.join(missing)}")
        info("Provide all flags for programmatic mode, or no flags for interactive mode.")
        raise SystemExit(1)

    # No flags → interactive mode
    if not has_any:
        _interactive_attest()
        return

    # Flag mode (existing behavior)
    repo_root = get_repo_root()

    # Validate diff hash matches current staged changes
    current_hash = get_staged_diff_hash()
    if staged_diff_hash != current_hash:
        error("Diff hash mismatch — staged changes have changed since hash was computed")
        console.print(f"  Provided: {staged_diff_hash[:16]}...")
        console.print(f"  Current:  {current_hash[:16]}...")
        raise SystemExit(1)

    # Parse JSON options
    rules_data = None
    if rules:
        try:
            rules_data = json.loads(rules)
        except json.JSONDecodeError as e:
            error(f"Invalid --rules JSON: {e}")
            raise SystemExit(1)

    arch_changes_data = None
    if arch_changes:
        try:
            arch_changes_data = json.loads(arch_changes)
        except json.JSONDecodeError as e:
            error(f"Invalid --arch-changes JSON: {e}")
            raise SystemExit(1)

    # Auto-generate timestamp
    if not timestamp:
        timestamp = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")

    # Build attestation document
    attestation = {
        "staged_diff_hash": staged_diff_hash,
        "timestamp": timestamp,
        "agent": agent,
        "agent_provider": provider,
        "agent_model": model,
    }

    if agent_id:
        attestation["agent_id"] = agent_id

    if rules_data:
        attestation["rules"] = rules_data

    # Merge architecture change primitives as top-level keys
    if arch_changes_data:
        for key in ARCH_PRIMITIVES:
            if key in arch_changes_data:
                attestation[key] = arch_changes_data[key]

    attestation["summary"] = summary

    # Write as YAML frontmatter into .gjalla/
    gjalla_dir = repo_root / ".gjalla"
    gjalla_dir.mkdir(parents=True, exist_ok=True)
    attestation_path = gjalla_dir / ".commit-attestation.yaml"
    yaml_body = yaml.dump(attestation, default_flow_style=False, sort_keys=False)
    attestation_path.write_text(f"---\n{yaml_body}---\n", encoding="utf-8")

    success("Created .gjalla/.commit-attestation.yaml")
    info("  Run 'git commit' — the pre-commit hook will verify the hash.")
