"""Load and organize static-feasible security checks for LLM-guided scans."""

from __future__ import annotations

import json
from pathlib import Path
from typing import Any

CHECKS_PATH = Path(__file__).with_name("security_checks.json")
DOC_FALLBACK_PATH = Path(__file__).resolve().parents[3] / "docs" / "owasp-static-checklist.md"


def load_checks() -> list[dict[str, Any]]:
    """Load static-feasible checks from the bundled JSON file."""
    checks: list[dict[str, Any]] = []
    if CHECKS_PATH.exists():
        try:
            payload = json.loads(CHECKS_PATH.read_text(encoding="utf-8"))
            checks = payload.get("checks", [])
        except Exception:
            checks = []
    if checks:
        return [c for c in checks if isinstance(c, dict)]
    if DOC_FALLBACK_PATH.exists():
        return _parse_checks_from_markdown(DOC_FALLBACK_PATH)
    return []


def _slugify(value: str) -> str:
    cleaned = "".join(ch.lower() if ch.isalnum() else "_" for ch in value)
    cleaned = "_".join([p for p in cleaned.split("_") if p])
    return cleaned or "check"


def _parse_checks_from_markdown(path: Path) -> list[dict[str, Any]]:
    section = ""
    checks: list[dict[str, Any]] = []
    used_ids: set[str] = set()
    for raw_line in path.read_text(encoding="utf-8").splitlines():
        line = raw_line.strip()
        if line.startswith("## "):
            section = line[3:].strip()
            continue
        if not line.startswith("|"):
            continue
        if "Static feasible?" in line or line.startswith("| ---"):
            continue
        parts = [p.strip() for p in line.strip("|").split("|")]
        if len(parts) < 5:
            continue
        check, static_feasible, current_support, justification, source = parts[:5]
        if static_feasible not in ("Yes", "Partial"):
            continue
        owasp_category = ""
        if len(check) >= 3 and check[0] == "A" and check[1:3].isdigit():
            owasp_category = check[:3]
        cid = _slugify(check)
        if cid in used_ids:
            suffix = 2
            while f"{cid}_{suffix}" in used_ids:
                suffix += 1
            cid = f"{cid}_{suffix}"
        used_ids.add(cid)
        checks.append({
            "id": cid,
            "title": check,
            "section": section,
            "owasp_category": owasp_category,
            "source": source,
            "static_feasible": static_feasible.lower(),
            "current_support": current_support.lower(),
            "notes": justification,
        })
    return checks


def build_profiles(checks: list[dict[str, Any]]) -> dict[str, list[str]]:
    """Build scan profiles grouped by section."""
    section_to_ids: dict[str, list[str]] = {}
    for c in checks:
        section = c.get("section") or ""
        cid = c.get("id")
        if not cid:
            continue
        section_to_ids.setdefault(section, []).append(cid)

    def ids_for_sections(sections: list[str]) -> list[str]:
        ids: list[str] = []
        for s in sections:
            ids.extend(section_to_ids.get(s, []))
        return ids

    profiles: dict[str, list[str]] = {
        "all_static": [c["id"] for c in checks if c.get("id")],
        "owasp_top10_static": ids_for_sections(["OWASP Top 10 (A01-A10)"]),
        "api_static": ids_for_sections(["API Security Testing (OWASP API Top 10 + API-specific checks)"]),
        "infra_static": ids_for_sections([
            "Network Security / Pentesting",
            "Cloud Security Testing (AWS, GCP, Azure)",
            "Container & DevOps Security",
        ]),
        "sast_static": ids_for_sections(["SAST - Static Application Security Testing"]),
        "auth_session_static": ids_for_sections([
            "Authentication & Authorization Security",
            "Session Security",
        ]),
        "data_exposure_static": ids_for_sections(["Data Exposure & PII Leakage"]),
        "deep_security": ids_for_sections([
            "OWASP Top 10 (A01-A10)",
            "API Security Testing (OWASP API Top 10 + API-specific checks)",
            "SAST - Static Application Security Testing",
            "Authentication & Authorization Security",
            "Session Security",
            "Data Exposure & PII Leakage",
            "Network Security / Pentesting",
            "Cloud Security Testing (AWS, GCP, Azure)",
            "Container & DevOps Security",
            "Business Logic Testing",
            "Compliance & Governance",
            "Threat Modeling",
            "Performance + Security Hybrid",
            "Summary Master List (Flat)",
        ]),
        "gigw_compliance": ids_for_sections([
            "GIGW Code Security",
            "GIGW Accessibility (WCAG 2.1)",
            "GIGW Quality",
            "GIGW Lifecycle Management",
            "GIGW Government Mandates",
        ]),
        "gigw_code_security": ids_for_sections(["GIGW Code Security"]),
        "gigw_accessibility": ids_for_sections(["GIGW Accessibility (WCAG 2.1)"]),
        "gigw_quality": ids_for_sections(["GIGW Quality"]),
        "gigw_lifecycle": ids_for_sections(["GIGW Lifecycle Management"]),
        "gigw_government_mandates": ids_for_sections(["GIGW Government Mandates"]),
        "wcag_21_aa": ids_for_sections(["Accessibility (WCAG 2.1 AA)"]),
        "wcag_22": ids_for_sections(["Accessibility (WCAG 2.2)"]),
        "saas_accessibility": ids_for_sections(["Accessibility (SaaS Patterns)"]),
        "general_accessibility": ids_for_sections([
            "Accessibility (WCAG 2.1 AA)",
            "Accessibility (WCAG 2.2)",
            "Accessibility (SaaS Patterns)",
        ]),
    }

    # Remove empty profiles
    return {name: ids for name, ids in profiles.items() if ids}


def get_checks_and_profiles() -> tuple[list[dict[str, Any]], dict[str, list[str]]]:
    checks = load_checks()
    profiles = build_profiles(checks)
    return checks, profiles


# ── Profile classification helpers ──────────────────────────────────
# Derived from build_profiles() definitions so there's a single source of truth.

_GIGW_SECTIONS = frozenset({
    "GIGW Code Security",
    "GIGW Accessibility (WCAG 2.1)",
    "GIGW Quality",
    "GIGW Lifecycle Management",
    "GIGW Government Mandates",
})

_A11Y_SECTIONS = frozenset({
    "Accessibility (WCAG 2.1 AA)",
    "Accessibility (WCAG 2.2)",
    "Accessibility (SaaS Patterns)",
})

# Map profile names → their constituent sections (mirrors build_profiles).
_PROFILE_SECTIONS: dict[str, frozenset[str]] = {
    # Security
    "owasp_top10_static": frozenset({"OWASP Top 10 (A01-A10)"}),
    "api_static": frozenset({"API Security Testing (OWASP API Top 10 + API-specific checks)"}),
    "infra_static": frozenset({"Network Security / Pentesting", "Cloud Security Testing (AWS, GCP, Azure)", "Container & DevOps Security"}),
    "sast_static": frozenset({"SAST - Static Application Security Testing"}),
    "auth_session_static": frozenset({"Authentication & Authorization Security", "Session Security"}),
    "data_exposure_static": frozenset({"Data Exposure & PII Leakage"}),
    "deep_security": frozenset({
        "OWASP Top 10 (A01-A10)", "API Security Testing (OWASP API Top 10 + API-specific checks)",
        "SAST - Static Application Security Testing", "Authentication & Authorization Security",
        "Session Security", "Data Exposure & PII Leakage", "Network Security / Pentesting",
        "Cloud Security Testing (AWS, GCP, Azure)", "Container & DevOps Security",
        "Business Logic Testing", "Compliance & Governance", "Threat Modeling",
        "Performance + Security Hybrid", "Summary Master List (Flat)",
    }),
    # GIGW
    "gigw_compliance": _GIGW_SECTIONS,
    "gigw_code_security": frozenset({"GIGW Code Security"}),
    "gigw_accessibility": frozenset({"GIGW Accessibility (WCAG 2.1)"}),
    "gigw_quality": frozenset({"GIGW Quality"}),
    "gigw_lifecycle": frozenset({"GIGW Lifecycle Management"}),
    "gigw_government_mandates": frozenset({"GIGW Government Mandates"}),
    # Accessibility
    "wcag_21_aa": frozenset({"Accessibility (WCAG 2.1 AA)"}),
    "wcag_22": frozenset({"Accessibility (WCAG 2.2)"}),
    "saas_accessibility": frozenset({"Accessibility (SaaS Patterns)"}),
    "general_accessibility": _A11Y_SECTIONS,
    # Catch-all
    "all_static": frozenset(),  # special: includes everything
}


def _profile_touches(profile_name: str, target_sections: frozenset[str]) -> bool:
    """Check if a profile's sections overlap with the target sections."""
    sections = _PROFILE_SECTIONS.get(profile_name, frozenset())
    return bool(sections & target_sections)


def is_security_profile(name: str) -> bool:
    """True if the profile is a pure security profile (not GIGW/a11y)."""
    if name in ("all_static",):
        return True
    return not is_gigw_profile(name) and not is_a11y_profile(name) and name in _PROFILE_SECTIONS


def is_gigw_profile(name: str) -> bool:
    return _profile_touches(name, _GIGW_SECTIONS)


def is_a11y_profile(name: str) -> bool:
    return _profile_touches(name, _A11Y_SECTIONS)


def is_compliance_profile(name: str) -> bool:
    """True if the profile requires compliance check results (GIGW or accessibility)."""
    return is_gigw_profile(name) or is_a11y_profile(name) or name == "all_static"


# ── GIGW pillar classification from check metadata ──────────────────

# Section name → GIGW pillar code
GIGW_SECTION_TO_PILLAR: dict[str, str] = {
    "GIGW Code Security": "GIGW-SEC",
    "GIGW Accessibility (WCAG 2.1)": "GIGW-A11Y",
    "GIGW Quality": "GIGW-QUAL",
    "GIGW Lifecycle Management": "GIGW-LIFE",
    "GIGW Government Mandates": "GIGW-GOV",
}


def build_check_section_index(checks: list[dict[str, Any]]) -> dict[str, str]:
    """Build a mapping from check_id → section name."""
    return {c["id"]: c.get("section", "") for c in checks if c.get("id")}


def classify_gigw_check(check_id: str, check_sections: dict[str, str]) -> str:
    """Classify a GIGW check into its pillar code using check metadata.

    Falls back to keyword matching only if the check isn't found in the index.
    """
    section = check_sections.get(check_id, "")
    pillar = GIGW_SECTION_TO_PILLAR.get(section)
    if pillar:
        return pillar
    # Fallback for checks not in the index (shouldn't happen, but safe)
    cl = check_id.lower()
    for kw, code in (
        ("accessibility", "GIGW-A11Y"), ("wcag", "GIGW-A11Y"), ("a11y", "GIGW-A11Y"),
        ("quality", "GIGW-QUAL"), ("performance", "GIGW-QUAL"),
        ("lifecycle", "GIGW-LIFE"), ("backup", "GIGW-LIFE"),
        ("government", "GIGW-GOV"), ("mandate", "GIGW-GOV"),
    ):
        if kw in cl:
            return code
    return "GIGW-SEC"
