"""Policy clause mappings and impact helpers."""

from __future__ import annotations

from typing import Dict, List, Tuple


# source_id -> list of rule-id prefixes that depend on the source.
SOURCE_RULE_PREFIXES: Dict[str, List[str]] = {
    "ios_app_store_review_guidelines": ["IOS-"],
    "apple_privacy_manifest": ["IOS-PRIV-", "IOS-ENT-"],
    "android_play_policy": ["AND-POL-", "AND-PERM-"],
    "android_target_sdk_requirements": ["AND-SDK-"],
    "owasp_top_10": ["WEB-OWASP-", "WEB-SEC-", "AND-SEC-", "IOS-SEC-"],
    "w3c_wcag_21": ["WEB-A11Y-", "CROSS-A11Y-"],
    "w3c_permissions_policy": ["WEB-SEC-"],
}

# Rule prefixes to canonical policy clause references.
POLICY_CLAUSE_BY_PREFIX: Dict[str, str] = {
    "IOS-PRIV-": "Apple App Store Review Guidelines 5.1 (Data Collection and Storage)",
    "IOS-SEC-": "Apple App Store Review Guidelines 2.5 (Software Requirements)",
    "IOS-ENT-": "Apple App Store Review Guidelines 2.5.1 (Public APIs and entitlements)",
    "IOS-META-": "Apple App Store Review Guidelines 2.1 (App Completeness)",
    "AND-SDK-": "Google Play Target API Level Requirements",
    "AND-PERM-": "Google Play Permissions Policy",
    "AND-POL-": "Google Play Developer Program Policy",
    "AND-SEC-": "OWASP Mobile Security Top 10",
    "AND-DATA-": "Google Play Data Safety Requirements",
    "WEB-OWASP-": "OWASP Top 10 (2021)",
    "WEB-A11Y-": "W3C WCAG 2.1",
    "WEB-SEC-": "OWASP Secure Headers / Browser Security Guidance",
    "CROSS-GDPR-": "GDPR Articles 6, 7, 13, 20, 37",
    "CROSS-COPPA-": "FTC COPPA Rule",
    "CROSS-HIPAA-": "HIPAA Security Rule",
    "CROSS-PCI-": "PCI DSS v4.0",
    "WEB-PRIV-": "GDPR / CCPA / CalOPPA Privacy Requirements",
    "CROSS-DATA-": "GDPR Article 17 (Right to Erasure) / App Store Data Deletion Requirements",
    "CROSS-A11Y-": "W3C WCAG 2.1",
    "SUPPLY-DEP-": "OWASP A06:2021 (Vulnerable and Outdated Components)",
    "SUPPLY-LIC-": "Open Source License Compliance (GPL/LGPL/AGPL)",
    "SUPPLY-API-": "Platform Deprecation and API Lifecycle Policy",
}

# Keyword-based classification for advanced engine findings (SG-*, CQ-*).
# Checked in order; first match wins.  Keywords are matched against the
# lowercased rule ID after stripping the SG-/CQ- prefix.
_ADVANCED_ENGINE_KEYWORDS: List[Tuple[str, str]] = [
    # A03:2021 — Injection
    ("sql-injection", "OWASP A03:2021 (Injection)"),
    ("sqli", "OWASP A03:2021 (Injection)"),
    ("nosql-injection", "OWASP A03:2021 (Injection)"),
    ("command-injection", "OWASP A03:2021 (Injection)"),
    ("child-process", "OWASP A03:2021 (Injection)"),
    ("detect-child-process", "OWASP A03:2021 (Injection)"),
    ("spawn-shell", "OWASP A03:2021 (Injection)"),
    ("dangerous-spawn", "OWASP A03:2021 (Injection)"),
    ("code-injection", "OWASP A03:2021 (Injection)"),
    ("eval", "OWASP A03:2021 (Injection)"),
    ("xss", "OWASP A03:2021 (Injection)"),
    ("cross-site-scripting", "OWASP A03:2021 (Injection)"),
    ("template-injection", "OWASP A03:2021 (Injection)"),
    ("ldap-injection", "OWASP A03:2021 (Injection)"),
    ("xpath-injection", "OWASP A03:2021 (Injection)"),
    ("header-injection", "OWASP A03:2021 (Injection)"),
    ("log-injection", "OWASP A03:2021 (Injection)"),
    # A01:2021 — Broken Access Control
    ("path-traversal", "OWASP A01:2021 (Broken Access Control)"),
    ("directory-traversal", "OWASP A01:2021 (Broken Access Control)"),
    ("csrf", "OWASP A01:2021 (Broken Access Control)"),
    ("csurf", "OWASP A01:2021 (Broken Access Control)"),
    ("open-redirect", "OWASP A01:2021 (Broken Access Control)"),
    ("idor", "OWASP A01:2021 (Broken Access Control)"),
    ("file-inclusion", "OWASP A01:2021 (Broken Access Control)"),
    ("cors", "OWASP A01:2021 (Broken Access Control)"),
    # A02:2021 — Cryptographic Failures
    ("insecure-transport", "OWASP A02:2021 (Cryptographic Failures)"),
    ("insecure-websocket", "OWASP A02:2021 (Cryptographic Failures)"),
    ("http-server", "OWASP A02:2021 (Cryptographic Failures)"),
    ("http-request", "OWASP A02:2021 (Cryptographic Failures)"),
    ("using-http", "OWASP A02:2021 (Cryptographic Failures)"),
    ("hardcoded-secret", "OWASP A02:2021 (Cryptographic Failures)"),
    ("hardcoded-password", "OWASP A02:2021 (Cryptographic Failures)"),
    ("weak-crypto", "OWASP A02:2021 (Cryptographic Failures)"),
    ("weak-hash", "OWASP A02:2021 (Cryptographic Failures)"),
    ("insecure-cipher", "OWASP A02:2021 (Cryptographic Failures)"),
    ("insecure-random", "OWASP A02:2021 (Cryptographic Failures)"),
    ("md5", "OWASP A02:2021 (Cryptographic Failures)"),
    ("sha1", "OWASP A02:2021 (Cryptographic Failures)"),
    ("ssl", "OWASP A02:2021 (Cryptographic Failures)"),
    ("tls", "OWASP A02:2021 (Cryptographic Failures)"),
    ("cleartext", "OWASP A02:2021 (Cryptographic Failures)"),
    # A04:2021 — Insecure Design
    ("race-condition", "OWASP A04:2021 (Insecure Design)"),
    ("mass-assignment", "OWASP A04:2021 (Insecure Design)"),
    # A05:2021 — Security Misconfiguration
    ("xxe", "OWASP A05:2021 (Security Misconfiguration)"),
    ("xml-external", "OWASP A05:2021 (Security Misconfiguration)"),
    ("misconfiguration", "OWASP A05:2021 (Security Misconfiguration)"),
    ("debug", "OWASP A05:2021 (Security Misconfiguration)"),
    ("verbose-error", "OWASP A05:2021 (Security Misconfiguration)"),
    ("stack-trace", "OWASP A05:2021 (Security Misconfiguration)"),
    # A06:2021 — Vulnerable Components
    ("vulnerable-component", "OWASP A06:2021 (Vulnerable and Outdated Components)"),
    ("outdated", "OWASP A06:2021 (Vulnerable and Outdated Components)"),
    # A07:2021 — Identification and Authentication Failures
    ("auth", "OWASP A07:2021 (Identification and Authentication Failures)"),
    ("jwt", "OWASP A07:2021 (Identification and Authentication Failures)"),
    ("session", "OWASP A07:2021 (Identification and Authentication Failures)"),
    ("credential", "OWASP A07:2021 (Identification and Authentication Failures)"),
    ("brute-force", "OWASP A07:2021 (Identification and Authentication Failures)"),
    # A08:2021 — Software and Data Integrity Failures
    ("deserialization", "OWASP A08:2021 (Software and Data Integrity Failures)"),
    ("prototype-pollution", "OWASP A08:2021 (Software and Data Integrity Failures)"),
    ("unsafe-yaml", "OWASP A08:2021 (Software and Data Integrity Failures)"),
    ("pickle", "OWASP A08:2021 (Software and Data Integrity Failures)"),
    # A09:2021 — Security Logging and Monitoring Failures
    ("unsafe-formatstring", "OWASP A09:2021 (Security Logging and Monitoring Failures)"),
    ("format-string", "OWASP A09:2021 (Security Logging and Monitoring Failures)"),
    ("logging", "OWASP A09:2021 (Security Logging and Monitoring Failures)"),
    # A10:2021 — Server-Side Request Forgery
    ("ssrf", "OWASP A10:2021 (Server-Side Request Forgery)"),
    ("server-side-request", "OWASP A10:2021 (Server-Side Request Forgery)"),
    # Generic security fallback — matches broad "security" keyword last
    ("security", "OWASP Top 10 (2021)"),
]


def _classify_advanced_engine_rule(rule_id: str) -> str:
    """Classify SG-*/CQ-* rule IDs into OWASP policy clauses via keyword matching."""
    # Strip the SG- or CQ- prefix and lowercase for matching
    rid_lower = (rule_id or "")[3:].lower().replace("_", "-").replace(".", "-")
    for keyword, clause in _ADVANCED_ENGINE_KEYWORDS:
        if keyword in rid_lower:
            return clause
    return ""


def policy_clause_for_rule(rule_id: str) -> str:
    rid = (rule_id or "").upper()
    # Try exact prefix match first (heuristic rules)
    for prefix, clause in POLICY_CLAUSE_BY_PREFIX.items():
        if rid.startswith(prefix):
            return clause
    # Try keyword-based classification for advanced engine findings
    if rid.startswith("SG-") or rid.startswith("CQ-"):
        return _classify_advanced_engine_rule(rule_id)
    return ""


def apply_policy_metadata(finding) -> None:
    if getattr(finding, "policy_clause", ""):
        return
    clause = policy_clause_for_rule(getattr(finding, "rule_id", ""))
    if clause:
        finding.policy_clause = clause


def impacted_rules_for_sources(source_ids: List[str], known_rule_ids: List[str]) -> List[str]:
    """Return likely impacted rule IDs for a list of changed policy source IDs."""
    out: List[str] = []
    for source_id in source_ids:
        prefixes = SOURCE_RULE_PREFIXES.get(source_id, [])
        for rid in known_rule_ids:
            if any(rid.startswith(p) for p in prefixes):
                out.append(rid)
    return sorted(set(out))
