"""Documentation generation functionality."""

from __future__ import annotations

import argparse
import json
from typing import Any, Iterable

import yaml

from cihub.cli_parsers.builder import build_parser
from cihub.utils.env_registry import CATEGORY_ORDER, get_all_env_vars
from cihub.utils.paths import hub_root

# =============================================================================
# Shared Helpers
# =============================================================================


def _format_type(prop: dict[str, Any]) -> str:
    if "type" in prop:
        value = prop["type"]
        if isinstance(value, list):
            return ", ".join(str(item) for item in value)
        return str(value)
    if "enum" in prop:
        return "enum"
    if "oneOf" in prop:
        options: list[str] = []
        for option in prop["oneOf"]:
            if isinstance(option, dict):
                option_type = _format_type(option)
                if option_type:
                    options.append(option_type)
        deduped: list[str] = []
        for option in options:
            if option not in deduped:
                deduped.append(option)
        return "|".join(deduped) if deduped else "oneOf"
    if "anyOf" in prop:
        return "anyOf"
    if "properties" in prop:
        return "object"
    return "unknown"


def _format_default(value: Any) -> str:
    if value is None:
        return ""
    if isinstance(value, (dict, list, bool, int, float)):
        return json.dumps(value)
    return str(value)


def _sanitize(value: str) -> str:
    return value.replace("|", "\\|").replace("\n", " ")


# =============================================================================
# CLI Reference Generation
# =============================================================================


def _subparsers(parser: argparse.ArgumentParser) -> dict[str, argparse.ArgumentParser]:
    for action in parser._actions:  # noqa: SLF001 - argparse internals
        if isinstance(action, argparse._SubParsersAction):  # noqa: SLF001
            return action.choices
    return {}


def _format_help(parser: argparse.ArgumentParser, title: str) -> str:
    help_text = parser.format_help().rstrip()
    return f"## {title}\n\n```\n{help_text}\n```\n"


def _render_parser_docs(parser: argparse.ArgumentParser, title: str) -> str:
    parts = [_format_help(parser, title)]
    for name, sub in _subparsers(parser).items():
        parts.append(_render_parser_docs(sub, f"{title} {name}"))
    return "\n".join(parts)


def render_cli_reference() -> str:
    """Generate CLI reference documentation from parser."""
    parser = build_parser()
    header = [
        "# CLI Reference",
        "",
        "Generated by `cihub docs generate`. Do not edit.",
        "",
    ]
    body = _render_parser_docs(parser, "cihub")
    return "\n".join(header) + body + "\n"


# =============================================================================
# Config Reference Generation
# =============================================================================

SCHEMA_SOURCE = "schema/ci-hub-config.schema.json"


def _resolve_ref(schema: dict[str, Any], ref: str) -> dict[str, Any]:
    if not ref.startswith("#/"):
        return {}
    node: Any = schema
    for part in ref[2:].split("/"):
        if not isinstance(node, dict):
            return {}
        node = node.get(part)
        if node is None:
            return {}
    return node if isinstance(node, dict) else {}


def _merge_schema(base: dict[str, Any], override: dict[str, Any]) -> dict[str, Any]:
    merged: dict[str, Any] = dict(base)
    for key, value in override.items():
        if key == "$ref":
            continue
        if key == "properties" and isinstance(value, dict):
            existing = merged.get("properties")
            if isinstance(existing, dict):
                merged["properties"] = {**existing, **value}
                continue
        if key == "required" and isinstance(value, list):
            existing_required = merged.get("required")
            if isinstance(existing_required, list):
                merged["required"] = list(dict.fromkeys([*existing_required, *value]))
                continue
        merged[key] = value
    return merged


def _resolve_schema(schema: dict[str, Any], prop: dict[str, Any]) -> dict[str, Any]:
    if "$ref" not in prop:
        return prop
    resolved = _resolve_ref(schema, prop["$ref"])
    if not resolved:
        return prop
    return _merge_schema(resolved, prop)


def _schema_entries(
    schema: dict[str, Any],
    root_schema: dict[str, Any],
    prefix: str = "",
) -> Iterable[dict[str, str]]:
    properties = schema.get("properties", {})
    required = set(schema.get("required", []))

    for name, prop in properties.items():
        prop = _resolve_schema(root_schema, prop)
        path = f"{prefix}.{name}" if prefix else name
        description = prop.get("description", "") or ""
        entry = {
            "path": path,
            "type": _format_type(prop),
            "required": "yes" if name in required else "no",
            "default": _format_default(prop.get("default")),
            "description": _sanitize(str(description)),
        }
        yield entry

        if "properties" in prop:
            yield from _schema_entries(prop, root_schema, path)
        elif prop.get("type") == "array" and isinstance(prop.get("items"), dict):
            items = _resolve_schema(root_schema, prop["items"])
            if "properties" in items:
                yield from _schema_entries(items, root_schema, f"{path}[]")


def render_config_reference() -> str:
    """Generate config reference documentation from schema."""
    schema_path = hub_root() / SCHEMA_SOURCE
    schema = json.loads(schema_path.read_text(encoding="utf-8"))

    lines = [
        "# Config Reference",
        "",
        "Generated by `cihub docs generate`. Do not edit.",
        "",
        f"Source: `{SCHEMA_SOURCE}`",
        "",
        "| Path | Type | Required | Default | Description |",
        "| --- | --- | --- | --- | --- |",
    ]

    for entry in _schema_entries(schema, schema):
        default = _sanitize(entry["default"])
        line = f"| `{entry['path']}` | {entry['type']} | {entry['required']} | {default} | {entry['description']} |"
        lines.append(line)

    return "\n".join(lines) + "\n"


# =============================================================================
# Workflow Reference Generation
# =============================================================================

WORKFLOWS_DIR = ".github/workflows"

# Workflow descriptions for user-friendly explanations
WORKFLOW_DESCRIPTIONS: dict[str, dict[str, str]] = {
    "hub-production-ci.yml": {
        "purpose": "Hub repo CI/CD",
        "when_to_use": "Runs automatically on the hub repo. You don't call this directly.",
        "target": "Hub repo only",
    },
    "hub-run-all.yml": {
        "purpose": "Central test runner",
        "when_to_use": (
            "Use this to test all repos from the hub without needing workflows in each repo (Central Mode)."
        ),
        "target": "All configured repos",
    },
    "hub-orchestrator.yml": {
        "purpose": "Distributed dispatch",
        "when_to_use": (
            "Use when repos have their own workflows and you want CI results "
            "in each repo's Actions tab (Distributed Mode)."
        ),
        "target": "Repos with workflows",
    },
    "hub-security.yml": {
        "purpose": "Security-focused dispatch",
        "when_to_use": "Use for security-only scans across repos without running full CI.",
        "target": "Repos with workflows",
    },
    "java-ci.yml": {
        "purpose": "Java reusable workflow",
        "when_to_use": (
            "Call from your Java repo's workflow for standardized CI (tests, coverage, linting, security)."
        ),
        "target": "Any Java repo",
    },
    "python-ci.yml": {
        "purpose": "Python reusable workflow",
        "when_to_use": (
            "Call from your Python repo's workflow for standardized CI (tests, coverage, linting, security)."
        ),
        "target": "Any Python repo",
    },
    "hub-ci.yml": {
        "purpose": "Language router",
        "when_to_use": "Call this if you want automatic language detection - it routes to java-ci or python-ci.",
        "target": "Any repo",
    },
    "smoke-test.yml": {
        "purpose": "Smoke test validation",
        "when_to_use": "Run manually to validate the hub works with fixture repos before releases.",
        "target": "Fixture repos",
    },
    "config-validate.yml": {
        "purpose": "Config schema validation",
        "when_to_use": "Runs automatically when you change configs. Validates YAML against schema.",
        "target": "Hub repo",
    },
    "release.yml": {
        "purpose": "Release automation",
        "when_to_use": "Triggered by pushing version tags (v*). Creates GitHub releases.",
        "target": "Hub repo",
    },
    "sync-templates.yml": {
        "purpose": "Template sync",
        "when_to_use": "Syncs template files to target repos. Run manually when templates change.",
        "target": "Template files",
    },
    "template-guard.yml": {
        "purpose": "Template drift detection",
        "when_to_use": "Runs on PRs to detect if templates have drifted from source.",
        "target": "Templates",
    },
    "kyverno-ci.yml": {
        "purpose": "Kyverno policy testing",
        "when_to_use": "Call from repos with Kyverno policies to validate them.",
        "target": "Policy repos",
    },
    "kyverno-validate.yml": {
        "purpose": "Kyverno validation",
        "when_to_use": "Validates Kyverno policies in the hub repo.",
        "target": "Hub repo",
    },
}


def _parse_workflow_triggers(on_section: Any) -> list[str]:
    """Extract trigger names from workflow 'on' section."""
    if isinstance(on_section, str):
        return [on_section]
    if isinstance(on_section, list):
        return on_section
    if isinstance(on_section, dict):
        return list(on_section.keys())
    return []


def _parse_workflow_inputs(on_section: Any) -> list[dict[str, Any]]:
    """Extract inputs from workflow_dispatch or workflow_call."""
    inputs: list[dict[str, Any]] = []
    if not isinstance(on_section, dict):
        return inputs

    # Check workflow_dispatch inputs
    dispatch = on_section.get("workflow_dispatch", {})
    if isinstance(dispatch, dict):
        dispatch_inputs = dispatch.get("inputs", {})
        if isinstance(dispatch_inputs, dict):
            for name, config in dispatch_inputs.items():
                if isinstance(config, dict):
                    inputs.append(
                        {
                            "name": name,
                            "type": config.get("type", "string"),
                            "required": config.get("required", False),
                            "default": config.get("default", ""),
                            "description": config.get("description", ""),
                            "source": "workflow_dispatch",
                        }
                    )

    # Check workflow_call inputs
    call = on_section.get("workflow_call", {})
    if isinstance(call, dict):
        call_inputs = call.get("inputs", {})
        if isinstance(call_inputs, dict):
            for name, config in call_inputs.items():
                if isinstance(config, dict):
                    inputs.append(
                        {
                            "name": name,
                            "type": config.get("type", "string"),
                            "required": config.get("required", False),
                            "default": config.get("default", ""),
                            "description": config.get("description", ""),
                            "source": "workflow_call",
                        }
                    )

    return inputs


def _format_triggers(triggers: list[str]) -> str:
    """Format triggers for display."""
    trigger_map = {
        "push": "push",
        "pull_request": "PR",
        "workflow_dispatch": "manual",
        "workflow_call": "reusable",
        "schedule": "schedule",
    }
    formatted = [trigger_map.get(t, t) for t in triggers]
    return ", ".join(formatted)


def _render_workflow_section(
    filename: str,
    workflow: dict[str, Any],
    desc: dict[str, str],
) -> str:
    """Render a single workflow section."""
    name = workflow.get("name", filename.replace(".yml", ""))
    # YAML parses "on:" as True (boolean) due to YAML 1.1 quirk
    on_section = workflow.get("on") or workflow.get(True) or {}  # type: ignore[call-overload]
    triggers = _parse_workflow_triggers(on_section)
    inputs = _parse_workflow_inputs(on_section)

    lines = [
        f"## {name}",
        "",
        f"**File:** `.github/workflows/{filename}`",
        "",
        f"{desc.get('purpose', 'No description')}.",
        "",
        f"**When to use:** {desc.get('when_to_use', 'See workflow comments.')}",
        "",
        "### Triggers",
        "",
        "| Trigger | Details |",
        "| ------- | ------- |",
    ]

    # Add trigger rows
    if isinstance(on_section, dict):
        for trigger in triggers:
            trigger_config = on_section.get(trigger, {})
            details = ""
            if trigger == "push" and isinstance(trigger_config, dict):
                branches = trigger_config.get("branches", [])
                paths = trigger_config.get("paths", [])
                if branches:
                    details = f"branches: {', '.join(branches)}"
                if paths:
                    details += f"; {len(paths)} path filters" if details else f"{len(paths)} path filters"
            elif trigger == "schedule" and isinstance(trigger_config, list):
                crons = [c.get("cron", "") for c in trigger_config if isinstance(c, dict)]
                details = f"cron: {', '.join(crons)}"
            elif trigger == "workflow_call":
                details = "Reusable workflow (called via `uses:`)"
            elif trigger == "workflow_dispatch":
                details = "Manual trigger with inputs"
            lines.append(f"| `{trigger}` | {details or '-'} |")
    else:
        for trigger in triggers:
            lines.append(f"| `{trigger}` | - |")

    # Add inputs section if any
    if inputs:
        lines.extend(
            [
                "",
                "### Inputs",
                "",
                "| Input | Type | Required | Default | Description |",
                "| ----- | ---- | -------- | ------- | ----------- |",
            ]
        )
        for inp in inputs:
            default = inp["default"]
            if default == "":
                default_str = "(empty)"
            elif isinstance(default, bool):
                default_str = str(default).lower()
            else:
                default_str = f"`{default}`"
            req = "yes" if inp["required"] else "no"
            desc_text = _sanitize(str(inp["description"]))
            lines.append(f"| `{inp['name']}` | {inp['type']} | {req} | {default_str} | {desc_text} |")

    lines.append("")
    lines.append("---")
    lines.append("")

    return "\n".join(lines)


# =============================================================================
# Environment Variable Reference Generation
# =============================================================================

# Environment variables are defined in cihub/utils/env_registry.py
# This prevents drift between runtime usage and documentation.


def render_env_reference() -> str:
    """Generate ENV.md environment variable reference from centralized registry."""
    env_vars = get_all_env_vars()

    lines = [
        "# Environment Variables Reference",
        "",
        "**Generated by:** `cihub docs generate`  ",
        "**Source:** `cihub/utils/env_registry.py`  ",
        "**Do not edit manually.**",
        "",
        "---",
        "",
        "## Quick Reference",
        "",
        "| Variable | Type | Default | Category | Description |",
        "| -------- | ---- | ------- | -------- | ----------- |",
    ]

    # Sort by category then name
    sorted_vars = sorted(env_vars, key=lambda v: (v.category, v.name))

    for var in sorted_vars:
        default = var.default or "-"
        desc = _sanitize(var.description)
        lines.append(f"| `{var.name}` | {var.var_type} | {default} | {var.category} | {desc} |")

    # Add category sections
    categories: dict[str, list] = {}
    for var in sorted_vars:
        if var.category not in categories:
            categories[var.category] = []
        categories[var.category].append(var)

    lines.extend(["", "---", ""])

    for cat in CATEGORY_ORDER:
        if cat not in categories:
            continue
        lines.extend([f"## {cat} Variables", ""])

        for var in categories[cat]:
            lines.extend(
                [
                    f"### `{var.name}`",
                    "",
                    f"**Type:** {var.var_type}  ",
                    f"**Default:** {var.default or '(none)'}",
                    "",
                    var.description,
                    "",
                ]
            )

    # Add usage examples
    lines.extend(
        [
            "---",
            "",
            "## Usage Examples",
            "",
            "### Enable Debug Output",
            "",
            "```bash",
            "CIHUB_DEBUG=true CIHUB_VERBOSE=true cihub check",
            "```",
            "",
            "### Generate Triage Bundles",
            "",
            "```bash",
            "CIHUB_EMIT_TRIAGE=true cihub ci",
            "```",
            "",
            "### Skip a Specific Tool",
            "",
            "```bash",
            "CIHUB_RUN_MUTMUT=false cihub check --all",
            "```",
            "",
        ]
    )

    return "\n".join(lines)


def render_workflows_reference() -> str:
    """Generate WORKFLOWS.md from workflow YAML files."""
    workflows_dir = hub_root() / WORKFLOWS_DIR
    if not workflows_dir.exists():
        return "# Workflows Reference\n\nNo workflows found.\n"

    lines = [
        "# Workflows Reference",
        "",
        "**Generated from:** `.github/workflows/*.yml`  ",
        "**Regenerate with:** `cihub docs generate`  ",
        "",
        "---",
        "",
        "## Quick Reference",
        "",
        "| Workflow | Purpose | Trigger | When to Use |",
        "| -------- | ------- | ------- | ----------- |",
    ]

    # Collect workflow data
    workflow_data: list[tuple[str, dict[str, Any], dict[str, str]]] = []

    for yml_file in sorted(workflows_dir.glob("*.yml")):
        filename = yml_file.name
        try:
            content = yaml.safe_load(yml_file.read_text(encoding="utf-8"))
            if not isinstance(content, dict):
                continue
        except yaml.YAMLError:
            continue

        desc = WORKFLOW_DESCRIPTIONS.get(
            filename,
            {
                "purpose": "Custom workflow",
                "when_to_use": "See workflow comments.",
                "target": "-",
            },
        )

        # YAML parses "on:" as True (boolean) due to YAML 1.1 quirk
        on_section = content.get("on") or content.get(True) or {}
        triggers = _parse_workflow_triggers(on_section)

        workflow_data.append((filename, content, desc))

        # Add to quick reference table
        trigger_str = _format_triggers(triggers)
        purpose = desc.get("purpose", "-")
        when = desc.get("when_to_use", "-")
        # Truncate long descriptions for table
        if len(when) > 60:
            when = when[:57] + "..."
        lines.append(f"| `{filename}` | {purpose} | {trigger_str} | {when} |")

    lines.extend(["", "---", ""])

    # Add detailed sections for each workflow
    for filename, content, desc in workflow_data:
        lines.append(_render_workflow_section(filename, content, desc))

    # Add related docs
    lines.extend(
        [
            "## Related Documentation",
            "",
            "- [WORKFLOWS.md (Guide)](../guides/WORKFLOWS.md) - Usage guidance and setup",
            "- [CONFIG.md](CONFIG.md) - Configuration reference",
            "- [CLI.md](CLI.md) - CLI command reference",
            "- [TOOLS.md](TOOLS.md) - Tool documentation",
            "",
        ]
    )

    return "\n".join(lines)
