#!/usr/bin/env python3
"""Generate docs/snippets/provider-cards.mdx from provider getting-started pages.

Scans docs/user-guide/providers/<name>/getting-started-*.mdx, keeps only the
providers that Prowler Cloud and Prowler Local Server actually support (source of truth: the
`ProviderChoices` enum in api/src/backend/api/models.py — CLI-only providers
such as Linode/LLM/Scaleway/StackIT are excluded), reads the frontmatter
`title`, derives a display name, and emits a snippet exporting a
`ProviderCards` component. Wired into pre-commit so the snippet stays in sync
whenever a provider page or the API enum changes.
"""

from __future__ import annotations

import ast
import re
import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[2]
PROVIDERS_DIR = REPO_ROOT / "docs" / "user-guide" / "providers"
SNIPPET_PATH = REPO_ROOT / "docs" / "snippets" / "provider-cards.mdx"
API_MODELS_PATH = REPO_ROOT / "api" / "src" / "backend" / "api" / "models.py"

# Docs folder names that don't match the API enum key. Keep tiny — only rename
# entries when the docs folder disagrees with the API-side identifier.
DOCS_DIR_TO_API_KEY = {
    "microsoft365": "m365",
    "oci": "oraclecloud",
}

# Folder-name → Mintlify icon override. Providers not listed fall back to
# DEFAULT_ICON. Add an entry only when the default looks wrong for a provider.
ICON_OVERRIDES = {
    "alibabacloud": "cloud",
    "aws": "aws",
    "azure": "microsoft",
    "cloudflare": "cloudflare",
    "gcp": "google",
    "github": "github",
    "googleworkspace": "users",
    "iac": "code",
    "image": "docker",
    "kubernetes": "dharmachakra",
    "microsoft365": "briefcase",
    "mongodbatlas": "leaf",
    "oci": "database",
    "okta": "key",
    "openstack": "cubes",
    "vercel": "triangle",
}
DEFAULT_ICON = "cloud"

TITLE_RE = re.compile(r"^\s*title\s*:\s*['\"](?P<title>.+?)['\"]\s*$", re.MULTILINE)
NAME_CLEANUP_RE = re.compile(
    r"^Getting Started [Ww]ith (?:the )?(?P<name>.+?)(?: on Prowler)?(?: Provider)?$"
)


def app_supported_provider_keys() -> set[str]:
    """Return the set of provider keys declared in the API's ProviderChoices enum.

    Uses ast rather than regex so formatting changes, decorators, comments, or
    multi-line values in the enum body don't silently drop or invent providers.
    """
    tree = ast.parse(API_MODELS_PATH.read_text(encoding="utf-8"))
    for node in ast.walk(tree):
        if not (isinstance(node, ast.ClassDef) and node.name == "ProviderChoices"):
            continue
        keys: set[str] = set()
        for item in node.body:
            if not isinstance(item, ast.Assign):
                continue
            value = item.value
            # Django TextChoices members look like: NAME = "key", _("Label")
            # which parses as an ast.Tuple whose first element is the key.
            if isinstance(value, ast.Tuple) and value.elts:
                first = value.elts[0]
                if isinstance(first, ast.Constant) and isinstance(first.value, str):
                    keys.add(first.value)
        return keys
    raise RuntimeError(
        f"Could not locate ProviderChoices class in {API_MODELS_PATH.relative_to(REPO_ROOT)}"
    )


def extract_title(mdx_path: Path) -> str:
    text = mdx_path.read_text(encoding="utf-8")
    match = TITLE_RE.search(text)
    if not match:
        raise ValueError(f"No frontmatter title in {mdx_path}")
    return match.group("title")


def display_name(title: str) -> str:
    match = NAME_CLEANUP_RE.match(title)
    return match.group("name") if match else title


def collect_providers() -> list[dict]:
    supported = app_supported_provider_keys()
    providers = []
    for provider_dir in sorted(PROVIDERS_DIR.iterdir()):
        if not provider_dir.is_dir():
            continue
        api_key = DOCS_DIR_TO_API_KEY.get(provider_dir.name, provider_dir.name)
        if api_key not in supported:
            continue
        pages = sorted(provider_dir.glob("getting-started-*.mdx"))
        if not pages:
            continue
        page = pages[0]
        name = display_name(extract_title(page))
        href = f"/user-guide/providers/{provider_dir.name}/{page.stem}"
        icon = ICON_OVERRIDES.get(provider_dir.name, DEFAULT_ICON)
        providers.append({"name": name, "href": href, "icon": icon})
    providers.sort(key=lambda p: p["name"].lower())
    return providers


def render_snippet(providers: list[dict]) -> str:
    cards = "\n".join(
        f'        <Card title="{p["name"]}" icon="{p["icon"]}" href="{p["href"]}" />'
        for p in providers
    )
    return (
        "{/* AUTO-GENERATED by docs/scripts/generate_provider_cards.py — do not edit by hand. */}\n"
        "{/* Regenerated on pre-commit whenever any provider getting-started page changes. */}\n"
        "\n"
        "export const ProviderCards = () => (\n"
        "    <Columns cols={3}>\n"
        f"{cards}\n"
        "    </Columns>\n"
        ");\n"
    )


def main() -> int:
    providers = collect_providers()
    if not providers:
        print("No provider getting-started pages found", file=sys.stderr)
        return 1
    new_content = render_snippet(providers)
    current = SNIPPET_PATH.read_text(encoding="utf-8") if SNIPPET_PATH.exists() else ""
    if new_content == current:
        return 0
    SNIPPET_PATH.write_text(new_content, encoding="utf-8")
    print(
        f"Regenerated {SNIPPET_PATH.relative_to(REPO_ROOT)} ({len(providers)} providers)"
    )
    return 1  # signal pre-commit that the file changed


if __name__ == "__main__":
    sys.exit(main())
