"""Cross-platform and supply chain compliance checks — run for all platforms."""

from __future__ import annotations

import re
from pathlib import Path
from typing import List

from ..engine.runner import Finding


def scan(project_root: Path) -> List[Finding]:
    """Run cross-platform and supply chain compliance checks."""
    findings: List[Finding] = []
    root = Path(project_root).resolve()

    source = _collect_all_source(root)

    findings.extend(_check_regulatory(root, source))
    findings.extend(_check_gdpr(root, source))
    findings.extend(_check_accessibility_contrast(root, source))
    findings.extend(_check_data_retention(root, source))
    findings.extend(_check_supply_chain(root, source))

    return findings


# ---------------------------------------------------------------------------
# File loaders
# ---------------------------------------------------------------------------

def _collect_all_source(root: Path, limit: int = 500) -> str:
    """Collect all source files for cross-platform pattern matching."""
    chunks: List[str] = []
    count = 0
    skip_dirs = [
        "node_modules/", "dist/", "build/", ".next/", ".nuxt/",
        "DerivedData", "Pods/", ".build/", ".gradle/",
    ]
    extensions = (
        "*.swift", "*.m", "*.kt", "*.java",
        "*.js", "*.ts", "*.jsx", "*.tsx",
        "*.py", "*.rb", "*.go", "*.rs",
        "*.html", "*.vue", "*.astro",
        "*.xml", "*.plist", "*.yaml", "*.yml", "*.json",
    )
    for ext in extensions:
        for src in root.rglob(ext):
            rel = str(src)
            if any(skip in rel for skip in skip_dirs):
                continue
            try:
                chunks.append(src.read_text(errors="ignore"))
            except OSError:
                continue
            count += 1
            if count >= limit:
                return "\n".join(chunks)
    return "\n".join(chunks)


# ---------------------------------------------------------------------------
# Regulatory Compliance (CROSS-*)
# ---------------------------------------------------------------------------

def _check_regulatory(root: Path, source: str) -> List[Finding]:
    findings: List[Finding] = []

    # CROSS-COPPA-001 — COPPA (Children's Apps)
    children_indicators = [
        r"(?i)children|child|kid|minor|under.?13|parental.?consent",
        r"(?i)age.?gate|ageGate|age_gate|age.?verification",
        r"(?i)kCFStreamPropertySSLSettings",  # iOS
        r"(?i)isChildDirected|child_directed|tagForChildDirectedTreatment",
    ]
    coppa_compliance = [
        r"(?i)coppa|parental.?consent|verifiable.?parental",
        r"(?i)age.?verification|age.?gate|ageGate",
    ]
    has_children_content = any(re.search(p, source) for p in children_indicators)
    has_coppa_compliance = any(re.search(p, source) for p in coppa_compliance)
    if has_children_content and not has_coppa_compliance:
        findings.append(Finding(
            rule_id="CROSS-COPPA-001",
            severity="FAIL",
            message="App appears to target children but no COPPA compliance mechanisms detected",
            fix="Implement age gating, parental consent flows, and limit data collection per COPPA requirements.",
            reference="FTC COPPA Rule — https://www.ftc.gov/legal-library/browse/rules/childrens-online-privacy-protection-rule-coppa",
        ))

    # CROSS-HIPAA-001 — HIPAA markers
    health_data_patterns = [
        r"(?i)(?:patient|medical|diagnosis|prescription|health.?record|PHI|protected.?health)",
        r"(?i)(?:HL7|FHIR|DICOM|ICD.?10|CPT.?code)",
        r"(?i)(?:HKHealthStore|HealthKit|healthkit)",
    ]
    hipaa_compliance_patterns = [
        r"(?i)(?:hipaa|HIPAA|baa|business.?associate|encryption.?at.?rest)",
        r"(?i)(?:audit.?log|access.?control|data.?encryption)",
    ]
    has_health_data = any(re.search(p, source) for p in health_data_patterns)
    has_hipaa = any(re.search(p, source) for p in hipaa_compliance_patterns)
    if has_health_data and not has_hipaa:
        findings.append(Finding(
            rule_id="CROSS-HIPAA-001",
            severity="WARN",
            message="Health/medical data handling detected without HIPAA compliance markers",
            fix="Ensure HIPAA compliance: encryption at rest/transit, access controls, audit logging, BAA with cloud providers.",
            reference="HHS HIPAA Security Rule — https://www.hhs.gov/hipaa/for-professionals/security/index.html",
        ))

    # CROSS-PCI-001 — PCI-DSS (Payment data)
    payment_patterns = [
        r"(?i)(?:credit.?card|card.?number|cvv|cvc|pan\b|card.?holder)",
        r"(?i)(?:stripe|braintree|adyen|square|paypal)",
        r"(?i)(?:payment|checkout|billing)",
    ]
    pci_safe_patterns = [
        r"(?i)(?:stripe\.js|elements|paymentIntent|PaymentSheet|STPPaymentHandler)",
        r"(?i)(?:braintree\.dropin|hosted.?fields|tokenize)",
        r"(?i)(?:pci.?dss|pci.?compliant|tokenized)",
    ]
    has_payments = any(re.search(p, source) for p in payment_patterns)
    has_pci_safe = any(re.search(p, source) for p in pci_safe_patterns)
    # Only warn if payment handling is detected but no tokenization/hosted fields
    raw_card_patterns = [
        r"(?i)card.?number\s*[:=]",
        r"(?i)cvv\s*[:=]",
        r"(?i)input.*(?:card|credit).*number",
    ]
    handles_raw_cards = any(re.search(p, source) for p in raw_card_patterns)
    if has_payments and handles_raw_cards and not has_pci_safe:
        findings.append(Finding(
            rule_id="CROSS-PCI-001",
            severity="FAIL",
            message="Raw payment card data handling detected without PCI-DSS tokenization",
            fix="Use a PCI-compliant payment processor (Stripe Elements, Braintree Hosted Fields) instead of handling raw card data.",
            reference="PCI DSS v4.0 — https://www.pcisecuritystandards.org/",
        ))

    return findings


# ---------------------------------------------------------------------------
# GDPR Compliance (CROSS-GDPR-*)
# ---------------------------------------------------------------------------

def _check_gdpr(root: Path, source: str) -> List[Finding]:
    findings: List[Finding] = []

    personal_data_patterns = [
        r"(?i)(?:user.?data|personal.?data|PII|profile|registration|sign.?up)",
        r"(?i)(?:createUser|registerUser|saveProfile|updateProfile)",
        r"(?i)(?:email|phone|address|birth|gender|name\s*[:=])",
    ]
    has_personal_data = any(re.search(p, source) for p in personal_data_patterns)

    # CROSS-GDPR-001 — Right to data portability (Art. 20)
    export_patterns = [
        r"(?i)(?:export.?data|download.?data|data.?export|data.?download|data.?portability)",
        r"(?i)(?:takeout|my.?data|request.?data|subject.?access.?request)",
        r"(?i)(?:toJSON|toCSV|data.?dump|generate.?report.*(?:user|personal))",
    ]
    has_export = any(re.search(p, source) for p in export_patterns)
    if has_personal_data and not has_export:
        findings.append(Finding(
            rule_id="CROSS-GDPR-001",
            severity="WARN",
            message="Personal data collection detected but no data export/portability mechanism found",
            fix="Implement data export functionality allowing users to download their data in a portable format (JSON, CSV).",
            reference="GDPR Article 20 — Right to Data Portability",
        ))

    # CROSS-GDPR-002 — Consent before processing (Art. 6/7)
    auto_tracking_patterns = [
        r"(?i)(?:gtag|ga|analytics|fbq|_paq)\s*\(\s*['\"](?:init|create|config|track)['\"]",
        r"(?i)(?:GoogleAnalytics|FirebaseAnalytics\.logEvent|Amplitude\.init|Mixpanel\.init)",
        r"(?i)(?:useEffect|componentDidMount|ngOnInit|mounted|onMount).*(?:analytics|tracking|gtag)",
    ]
    consent_gate_patterns = [
        r"(?i)(?:if.*consent|consent.*then|hasConsent|isConsented|consentGiven)",
        r"(?i)(?:waitForConsent|onConsent|consentCallback|consent.?check)",
        r"(?i)(?:OptanonWrapper|OneTrust|cookiebot|CookieConsent.*accept)",
    ]
    has_auto_tracking = any(re.search(p, source) for p in auto_tracking_patterns)
    has_consent_gate = any(re.search(p, source) for p in consent_gate_patterns)
    if has_auto_tracking and not has_consent_gate:
        findings.append(Finding(
            rule_id="CROSS-GDPR-002",
            severity="FAIL",
            message="Analytics/tracking appears to initialize without waiting for user consent",
            fix="Gate analytics initialization behind user consent. Do not load tracking scripts until consent is granted.",
            reference="GDPR Articles 6 & 7 — Lawfulness of processing / Conditions for consent",
        ))

    # CROSS-GDPR-003 — Privacy contact / DPO (Art. 13/37)
    dpo_patterns = [
        r"(?i)(?:data.?protection.?officer|DPO\b|privacy.?officer|privacy.?contact)",
        r"(?i)(?:dpo@|privacy@|dataprotection@|gdpr@)",
        r"(?i)(?:privacy.?team|data.?protection.?team|privacy.?department)",
    ]
    has_dpo = any(re.search(p, source) for p in dpo_patterns)
    # Also check privacy policy pages
    privacy_pages = list(root.rglob("*privacy*"))
    privacy_pages = [p for p in privacy_pages if "node_modules" not in str(p) and "build" not in str(p)]
    for pp in privacy_pages[:5]:
        try:
            pp_content = pp.read_text(errors="ignore")
        except OSError:
            continue
        if any(re.search(p, pp_content) for p in dpo_patterns):
            has_dpo = True
            break
    if has_personal_data and not has_dpo:
        findings.append(Finding(
            rule_id="CROSS-GDPR-003",
            severity="INFO",
            message="Personal data processing detected but no Data Protection Officer or privacy contact found",
            fix="Include DPO or privacy contact information in your privacy policy (required for certain controllers/processors).",
            reference="GDPR Articles 13(1)(b) & 37 — DPO designation",
        ))

    return findings


# ---------------------------------------------------------------------------
# Accessibility — Color Contrast (CROSS-A11Y-001)
# ---------------------------------------------------------------------------

def _check_accessibility_contrast(root: Path, source: str) -> List[Finding]:
    findings: List[Finding] = []

    # Check CSS/styles for low contrast color pairs
    # Look for very light text on white or very dark text on black backgrounds
    low_contrast_patterns = [
        r'color\s*:\s*#(?:fff|FFF|ffffff|FFFFFF)[^;]*;[^}]*background[^:]*:\s*#(?:fff|FFF|ffffff|FFFFFF|fafafa|f5f5f5)',
        r'color\s*:\s*#(?:eee|ddd|ccc|bbb|aaa)[^;]*;',
        r'color\s*:\s*(?:lightgray|lightgrey|silver)\b',
    ]
    # Also check for explicit low-contrast declarations
    has_contrast_issue = any(re.search(p, source, re.IGNORECASE) for p in low_contrast_patterns)

    # Check for CSS files specifically
    css_chunks: List[str] = []
    for css_file in root.rglob("*.css"):
        if any(skip in str(css_file) for skip in ["node_modules/", "dist/", "build/"]):
            continue
        try:
            css_chunks.append(css_file.read_text(errors="ignore"))
        except OSError:
            continue
    css_content = "\n".join(css_chunks)

    if not has_contrast_issue:
        has_contrast_issue = any(re.search(p, css_content, re.IGNORECASE) for p in low_contrast_patterns)

    if has_contrast_issue:
        findings.append(Finding(
            rule_id="CROSS-A11Y-001",
            severity="WARN",
            message="Potential low color contrast detected in styles",
            fix="Ensure text meets WCAG 2.1 contrast ratios: 4.5:1 for normal text, 3:1 for large text.",
            reference="WCAG 2.1 — 1.4.3 Contrast (Minimum)",
        ))

    return findings


# ---------------------------------------------------------------------------
# Data Retention (CROSS-DATA-001)
# ---------------------------------------------------------------------------

def _check_data_retention(root: Path, source: str) -> List[Finding]:
    findings: List[Finding] = []

    # Check if the app collects data but has no data retention/deletion policy
    data_collection_patterns = [
        r"(?i)(?:firebase|analytics|tracking|telemetry)",
        r"(?i)(?:user.?data|personal.?data|PII|personally.?identifiable)",
        r"(?i)(?:database|collection|store|persist)",
    ]
    retention_patterns = [
        r"(?i)(?:data.?retention|retention.?policy|data.?deletion|delete.?account)",
        r"(?i)(?:right.?to.?erasure|right.?to.?delete|forget.?me|data.?purge)",
        r"(?i)(?:TTL|time.?to.?live|expir|auto.?delete|scheduled.?deletion)",
    ]
    has_data_collection = any(re.search(p, source) for p in data_collection_patterns)
    has_retention_policy = any(re.search(p, source) for p in retention_patterns)
    if has_data_collection and not has_retention_policy:
        findings.append(Finding(
            rule_id="CROSS-DATA-001",
            severity="INFO",
            message="Data collection detected but no data retention or deletion policy found",
            fix="Implement data retention policies and account deletion capability (required by GDPR Art. 17 and App Store guidelines).",
            reference="GDPR Article 17 — Right to Erasure",
        ))

    return findings


# ---------------------------------------------------------------------------
# Supply Chain (SUPPLY-*)
# ---------------------------------------------------------------------------

def _check_supply_chain(root: Path, source: str) -> List[Finding]:
    findings: List[Finding] = []

    # SUPPLY-LIC-001 — GPL license in potentially proprietary app
    license_files = list(root.rglob("LICENSE*")) + list(root.rglob("LICENCE*"))
    license_files = [f for f in license_files if "node_modules/" not in str(f)]
    # Check for GPL dependencies
    package_json = root / "package.json"
    podfile = root / "Podfile"
    build_gradle_files = list(root.rglob("build.gradle")) + list(root.rglob("build.gradle.kts"))
    dep_content = ""
    for dep_file in [package_json, podfile] + build_gradle_files:
        if dep_file.exists():
            try:
                dep_content += dep_file.read_text(errors="ignore") + "\n"
            except OSError:
                continue
    # Check node_modules for GPL licenses
    gpl_indicators = []
    node_modules = root / "node_modules"
    if node_modules.exists():
        for pkg_json in node_modules.glob("*/package.json"):
            try:
                import json
                pkg_data = json.loads(pkg_json.read_text(errors="ignore"))
                license_val = pkg_data.get("license", "")
                if isinstance(license_val, str) and re.search(r'\bGPL\b', license_val):
                    gpl_indicators.append(pkg_data.get("name", str(pkg_json.parent.name)))
            except (json.JSONDecodeError, OSError):
                continue
    if gpl_indicators:
        findings.append(Finding(
            rule_id="SUPPLY-LIC-001",
            severity="WARN",
            message=f"GPL-licensed dependencies found: {', '.join(gpl_indicators[:5])}",
            fix="Review GPL dependencies for license compatibility. GPL code in proprietary apps may require open-sourcing your code.",
            reference="GNU GPL License — https://www.gnu.org/licenses/gpl-3.0.html",
        ))

    # SUPPLY-DEP-001 — Outdated SDK version checks
    # Check for known outdated major versions in dependency declarations
    outdated_patterns = [
        (r'"react"\s*:\s*"[\^~]?(?:16|15|14)\.', "React (< 17)"),
        (r'"angular/core"\s*:\s*"[\^~]?(?:[0-9]|1[0-4])\.', "Angular (< 15)"),
        (r'"vue"\s*:\s*"[\^~]?2\.', "Vue.js 2.x"),
        (r'"next"\s*:\s*"[\^~]?(?:[0-9]|1[0-2])\.', "Next.js (< 13)"),
        (r'"express"\s*:\s*"[\^~]?[0-3]\.', "Express (< 4)"),
        (r'compileSdkVersion\s+(?:2[0-9]|3[0-2])', "Android compileSdk (< 33)"),
    ]
    outdated_found = []
    for pattern, label in outdated_patterns:
        if re.search(pattern, dep_content + source):
            outdated_found.append(label)
    if outdated_found:
        findings.append(Finding(
            rule_id="SUPPLY-DEP-001",
            severity="INFO",
            message=f"Outdated framework/SDK versions detected: {', '.join(outdated_found)}",
            fix="Update to the latest stable versions for security patches and feature support.",
            reference="OWASP A06:2021 — Vulnerable and Outdated Components",
        ))

    # SUPPLY-API-001 — Deprecated API usage
    deprecated_api_patterns = [
        (r'\bcomponentWillMount\b', "React componentWillMount (deprecated)"),
        (r'\bcomponentWillReceiveProps\b', "React componentWillReceiveProps (deprecated)"),
        (r'\bUIWebView\b', "iOS UIWebView (deprecated, use WKWebView)"),
        (r'\bABAddressBook\b', "iOS ABAddressBook (deprecated, use CNContactStore)"),
        (r'\bUIAlertView\b', "iOS UIAlertView (deprecated, use UIAlertController)"),
        (r'\bAsyncTask\b', "Android AsyncTask (deprecated, use coroutines/RxJava)"),
        (r'\bIntentService\b', "Android IntentService (deprecated, use WorkManager)"),
        (r'\bgetExternalStorageDirectory\b', "Android getExternalStorageDirectory (deprecated)"),
    ]
    deprecated_found = []
    for pattern, label in deprecated_api_patterns:
        if re.search(pattern, source):
            deprecated_found.append(label)
    if deprecated_found:
        findings.append(Finding(
            rule_id="SUPPLY-API-001",
            severity="INFO",
            message=f"Deprecated API usage detected: {', '.join(deprecated_found[:5])}",
            fix="Migrate to modern API replacements to ensure continued platform support.",
            reference="Platform deprecation documentation",
        ))

    return findings
