"""Android compliance scanner — parses build.gradle, AndroidManifest.xml, and Java/Kotlin source."""

from __future__ import annotations

import re
from pathlib import Path
from typing import List, Optional
from xml.etree import ElementTree as ET

from ..engine.runner import Finding


def scan(project_root: Path) -> List[Finding]:
    """Run all Android compliance checks and return findings."""
    findings: List[Finding] = []
    root = Path(project_root).resolve()

    gradle_text = _load_gradle(root)
    manifest = _load_manifest(root)
    manifest_text = _load_manifest_text(root)
    source_contents = _collect_source(root)

    findings.extend(_check_sdk_versions(gradle_text, source_contents))
    findings.extend(_check_permissions(manifest, manifest_text, source_contents))
    findings.extend(_check_security(manifest, manifest_text, gradle_text, source_contents))
    findings.extend(_check_secrets(source_contents, gradle_text))
    findings.extend(_check_data_safety(root, gradle_text, source_contents))
    findings.extend(_check_policy(root, gradle_text, source_contents))

    return findings


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

def _load_gradle(root: Path) -> str:
    """Concatenate all build.gradle / build.gradle.kts files."""
    chunks: List[str] = []
    for ext in ("build.gradle", "build.gradle.kts"):
        for f in root.rglob(ext):
            if any(p in str(f) for p in ["build/", ".gradle/"]):
                continue
            try:
                chunks.append(f.read_text(errors="ignore"))
            except OSError:
                continue
    return "\n".join(chunks)


def _safe_parse_xml(path: Path) -> Optional[ET.Element]:
    """Parse XML safely to prevent XXE attacks."""
    try:
        parser = ET.XMLParser()
        tree = ET.parse(path, parser=parser)
        return tree.getroot()
    except ET.ParseError:
        return None


def _load_manifest(root: Path) -> Optional[ET.Element]:
    """Find and parse the first AndroidManifest.xml."""
    for mf in root.rglob("AndroidManifest.xml"):
        if "build/" in str(mf) or ".gradle/" in str(mf):
            continue
        # Prevent symlink escape
        try:
            resolved = mf.resolve()
            if not str(resolved).startswith(str(root.resolve())):
                continue
        except OSError:
            continue
        result = _safe_parse_xml(mf)
        if result is not None:
            return result
    return None


def _load_manifest_text(root: Path) -> str:
    """Return raw text of the first AndroidManifest.xml."""
    for mf in root.rglob("AndroidManifest.xml"):
        if "build/" in str(mf) or ".gradle/" in str(mf):
            continue
        try:
            return mf.read_text(errors="ignore")
        except OSError:
            continue
    return ""


def _collect_source(root: Path, limit: int = 500) -> str:
    """Concatenate Kotlin/Java source for pattern matching."""
    chunks: List[str] = []
    count = 0
    for ext in ("*.kt", "*.java", "*.dart", "*.cs"):
        for src in root.rglob(ext):
            if any(p in str(src) for p in ["build/", ".gradle/", ".dart_tool/"]):
                continue
            try:
                chunks.append(src.read_text(errors="ignore"))
            except OSError:
                continue
            count += 1
            if count >= limit:
                break
    return "\n".join(chunks)


# ---------------------------------------------------------------------------
# SDK Version checks (AND-SDK-*)
# ---------------------------------------------------------------------------

_MIN_TARGET_SDK = 35  # Android 15 — required for new apps/updates since Aug 2025


def _extract_sdk_value(gradle: str, field: str) -> Optional[int]:
    """Extract an integer SDK value from gradle text."""
    m = re.search(rf'{field}\s*=?\s*(\d+)', gradle)
    if m:
        return int(m.group(1))
    return None


def _check_sdk_versions(gradle: str, source: str = "") -> List[Finding]:
    findings: List[Finding] = []
    if not gradle:
        return findings

    target_sdk = _extract_sdk_value(gradle, "targetSdk(?:Version)?")
    compile_sdk = _extract_sdk_value(gradle, "compileSdk(?:Version)?")

    if target_sdk is not None and target_sdk < _MIN_TARGET_SDK:
        findings.append(Finding(
            rule_id="AND-SDK-001",
            severity="FAIL",
            message=f"Target SDK {target_sdk} is below Google Play minimum ({_MIN_TARGET_SDK})",
            fix=f"Update targetSdkVersion to at least {_MIN_TARGET_SDK} in build.gradle.",
            reference="https://developer.android.com/google/play/requirements/target-sdk",
        ))

    if target_sdk is not None and compile_sdk is not None and compile_sdk < target_sdk:
        findings.append(Finding(
            rule_id="AND-SDK-002",
            severity="WARN",
            message=f"compileSdkVersion ({compile_sdk}) is less than targetSdkVersion ({target_sdk})",
            fix="Ensure compileSdkVersion >= targetSdkVersion in build.gradle.",
            reference="Android Developer Documentation",
        ))

    # AND-SDK-003 — Third-party SDK data privacy compliance
    third_party_sdks = [
        # Kotlin / Java / Gradle
        (r"com\.facebook", "Facebook SDK"),
        (r"com\.google\.firebase", "Firebase SDK"),
        (r"com\.google\.android\.gms", "Google Play Services"),
        (r"com\.appsflyer", "AppsFlyer"),
        (r"com\.adjust\.sdk", "Adjust SDK"),
        (r"com\.braze", "Braze SDK"),
        # Flutter / Dart
        (r"firebase_core|firebase_analytics|firebase_crashlytics", "Firebase SDK"),
        (r"facebook_app_events|flutter_facebook_auth", "Facebook SDK"),
        (r"appsflyer_sdk", "AppsFlyer"),
        (r"adjust_sdk", "Adjust SDK"),
        # React Native
        (r"@react-native-firebase", "Firebase SDK"),
        (r"react-native-fbsdk", "Facebook SDK"),
        (r"react-native-appsflyer", "AppsFlyer"),
        (r"react-native-adjust", "Adjust SDK"),
    ]
    detected_sdks = list(dict.fromkeys(
        label for pattern, label in third_party_sdks if re.search(pattern, gradle + source)
    ))
    if detected_sdks:
        findings.append(Finding(
            rule_id="AND-SDK-003",
            severity="INFO",
            message=f"Third-party SDKs detected that require data disclosure: {', '.join(detected_sdks)}",
            fix="Ensure all third-party SDK data collection is disclosed in your Google Play Data Safety section.",
            reference="https://support.google.com/googleplay/android-developer/answer/10787469",
        ))

    return findings


# ---------------------------------------------------------------------------
# Permissions (AND-PERM-*)
# ---------------------------------------------------------------------------

ANDROID_NS = "{http://schemas.android.com/apk/res/android}"

_DANGEROUS_PERMISSIONS = {
    "android.permission.READ_CONTACTS",
    "android.permission.WRITE_CONTACTS",
    "android.permission.READ_CALENDAR",
    "android.permission.WRITE_CALENDAR",
    "android.permission.RECORD_AUDIO",
    "android.permission.CAMERA",
    "android.permission.READ_EXTERNAL_STORAGE",
    "android.permission.WRITE_EXTERNAL_STORAGE",
    "android.permission.BODY_SENSORS",
    "android.permission.ACCESS_FINE_LOCATION",
    "android.permission.ACCESS_COARSE_LOCATION",
    "android.permission.READ_PHONE_STATE",
    "android.permission.CALL_PHONE",
    "android.permission.READ_MEDIA_IMAGES",
    "android.permission.READ_MEDIA_VIDEO",
    "android.permission.READ_MEDIA_AUDIO",
    "android.permission.POST_NOTIFICATIONS",
    "android.permission.NEARBY_WIFI_DEVICES",
}

_SMS_CALL_PERMISSIONS = {
    "android.permission.READ_SMS",
    "android.permission.SEND_SMS",
    "android.permission.RECEIVE_SMS",
    "android.permission.READ_CALL_LOG",
    "android.permission.WRITE_CALL_LOG",
    "android.permission.PROCESS_OUTGOING_CALLS",
}


def _get_permissions(manifest: Optional[ET.Element]) -> set:
    if manifest is None:
        return set()
    perms = set()
    for elem in manifest.iter("uses-permission"):
        name = elem.get(f"{ANDROID_NS}name", "")
        if name:
            perms.add(name)
    return perms


def _check_permissions(manifest: Optional[ET.Element], manifest_text: str, source: str = "") -> List[Finding]:
    findings: List[Finding] = []
    perms = _get_permissions(manifest)

    dangerous_found = perms & _DANGEROUS_PERMISSIONS
    if dangerous_found:
        findings.append(Finding(
            rule_id="AND-PERM-001",
            severity="FAIL",
            message=f"Dangerous permissions detected: {', '.join(sorted(dangerous_found))}",
            fix="Ensure each dangerous permission is justified and runtime permission requests are implemented.",
            reference="https://support.google.com/googleplay/android-developer/answer/9888170",
        ))

    has_bg_location = "android.permission.ACCESS_BACKGROUND_LOCATION" in perms
    has_fg_location = (
        "android.permission.ACCESS_FINE_LOCATION" in perms
        or "android.permission.ACCESS_COARSE_LOCATION" in perms
    )
    if has_bg_location and not has_fg_location:
        findings.append(Finding(
            rule_id="AND-PERM-002",
            severity="FAIL",
            message="Background location requested without foreground location permission",
            fix="Request ACCESS_FINE_LOCATION before ACCESS_BACKGROUND_LOCATION.",
            reference="Google Play Background Location Policy",
        ))

    sms_call_found = perms & _SMS_CALL_PERMISSIONS
    if sms_call_found:
        findings.append(Finding(
            rule_id="AND-PERM-003",
            severity="FAIL",
            message=f"SMS/Call Log permissions detected: {', '.join(sorted(sms_call_found))}",
            fix="SMS and Call Log permissions require a declaration form on Google Play. Remove if not core functionality.",
            reference="Google Play SMS/Call Log Policy",
        ))

    # AND-PERM-004 — Biometric permission check
    has_biometric_perm = "android.permission.USE_BIOMETRIC" in perms
    biometric_patterns = [
        # Kotlin / Java
        r"BiometricPrompt", r"BiometricManager", r"FingerprintManager",
        # Flutter / Dart
        r"local_auth", r"LocalAuthentication", r"authenticateWithBiometrics",
        # React Native
        r"react-native-biometrics", r"TouchID", r"FaceID",
        # .NET MAUI / Xamarin
        r"BiometricAuthentication", r"CrossFingerprint",
    ]
    uses_biometric = any(re.search(p, manifest_text + source) for p in biometric_patterns)
    if uses_biometric and not has_biometric_perm:
        findings.append(Finding(
            rule_id="AND-PERM-004",
            severity="WARN",
            message="Biometric API usage detected but USE_BIOMETRIC permission not declared",
            fix="Add <uses-permission android:name='android.permission.USE_BIOMETRIC'/> to AndroidManifest.xml.",
            reference="https://developer.android.com/training/sign-in/biometric-auth",
        ))

    # AND-PERM-005 — QUERY_ALL_PACKAGES restricted permission
    if "android.permission.QUERY_ALL_PACKAGES" in perms:
        findings.append(Finding(
            rule_id="AND-PERM-005",
            severity="FAIL",
            message="QUERY_ALL_PACKAGES is a restricted permission requiring Play Console declaration",
            fix="Remove QUERY_ALL_PACKAGES unless core functionality. Use targeted <queries> element instead.",
            reference="https://support.google.com/googleplay/android-developer/answer/10158779",
        ))

    # AND-PERM-006 — USE_EXACT_ALARM / SCHEDULE_EXACT_ALARM
    has_exact_alarm = (
        "android.permission.USE_EXACT_ALARM" in perms
        or "android.permission.SCHEDULE_EXACT_ALARM" in perms
    )
    if has_exact_alarm:
        findings.append(Finding(
            rule_id="AND-PERM-006",
            severity="WARN",
            message="Exact alarm permissions detected — restricted on API 34+",
            fix="USE_EXACT_ALARM is restricted to alarm/timer/calendar apps. Use inexact alarms unless precision is essential.",
            reference="https://developer.android.com/about/versions/14/changes/schedule-exact-alarms",
        ))

    # AND-PERM-007 — REQUEST_INSTALL_PACKAGES restricted permission
    if "android.permission.REQUEST_INSTALL_PACKAGES" in perms:
        findings.append(Finding(
            rule_id="AND-PERM-007",
            severity="FAIL",
            message="REQUEST_INSTALL_PACKAGES is a restricted permission requiring Play Console declaration",
            fix="Remove unless app installs APKs as core functionality. Requires a declaration form on Play Console.",
            reference="Google Play Permissions Policy — REQUEST_INSTALL_PACKAGES",
        ))

    # AND-PERM-008 — MANAGE_EXTERNAL_STORAGE restricted permission
    if "android.permission.MANAGE_EXTERNAL_STORAGE" in perms:
        findings.append(Finding(
            rule_id="AND-PERM-008",
            severity="FAIL",
            message="MANAGE_EXTERNAL_STORAGE is a restricted permission requiring Play Console declaration",
            fix="Use scoped storage or the Photo Picker API instead. Only file managers qualify for this permission.",
            reference="https://support.google.com/googleplay/android-developer/answer/9888170",
        ))

    return findings


# ---------------------------------------------------------------------------
# Security (AND-SEC-*)
# ---------------------------------------------------------------------------

def _check_security(
    manifest: Optional[ET.Element], manifest_text: str, gradle: str, source: str
) -> List[Finding]:
    findings: List[Finding] = []

    # AND-SEC-001 — Debuggable in release
    debuggable_manifest = re.search(r'android:debuggable\s*=\s*"true"', manifest_text)
    debuggable_gradle = re.search(r'release\s*\{[^}]*debuggable\s*=?\s*true', gradle, re.DOTALL)
    if debuggable_manifest or debuggable_gradle:
        findings.append(Finding(
            rule_id="AND-SEC-001",
            severity="FAIL",
            message="Debuggable build enabled in release configuration",
            fix="Set android:debuggable to false in release builds.",
            reference="Android Security Best Practices",
        ))

    # AND-SEC-002 — Cleartext traffic
    cleartext = re.search(r'android:usesCleartextTraffic\s*=\s*"true"', manifest_text)
    if cleartext:
        findings.append(Finding(
            rule_id="AND-SEC-002",
            severity="WARN",
            message="Cleartext (HTTP) network traffic is allowed",
            fix="Set android:usesCleartextTraffic to false or add a network_security_config.xml.",
            reference="Android Network Security Configuration",
        ))

    # AND-SEC-003 — SSL Certificate Pinning
    network_patterns = [
        # Kotlin / Java
        r"OkHttpClient", r"HttpURLConnection", r"Retrofit", r"Volley",
        # Flutter / Dart
        r"http\.Client", r"package:http\b", r"package:dio\b", r"Dio\(\)",
        # React Native
        r"react-native-ssl-pinning", r"fetch\(", r"axios",
        # .NET MAUI / Xamarin
        r"HttpClient", r"HttpClientHandler", r"WebRequest",
    ]
    pinning_patterns = [
        # Kotlin / Java
        r"CertificatePinner", r"network_security_config", r"NetworkSecurityConfig",
        r"TrustManager", r"X509TrustManager", r"pinnedCertificates",
        # Flutter / Dart
        r"SecurityContext", r"badCertificateCallback", r"http_certificate_pinning",
        # React Native
        r"ssl-pinning", r"TrustKit",
        # .NET MAUI / Xamarin
        r"ServerCertificateCustomValidationCallback", r"ServerCertificateValidationCallback",
    ]
    makes_network = any(re.search(p, source + gradle) for p in network_patterns)
    has_pinning = any(re.search(p, source + manifest_text) for p in pinning_patterns)
    if makes_network and not has_pinning:
        findings.append(Finding(
            rule_id="AND-SEC-003",
            severity="WARN",
            message="No SSL certificate pinning detected for network communications",
            fix="Implement SSL pinning via OkHttp CertificatePinner or network_security_config.xml.",
            reference="OWASP Mobile Security — M3 Insecure Communication",
        ))

    # AND-SEC-004 — Exported components without permission
    if manifest is not None:
        for tag in ["activity", "service", "receiver", "provider"]:
            for elem in manifest.iter(tag):
                exported = elem.get(f"{ANDROID_NS}exported", "")
                has_permission = elem.get(f"{ANDROID_NS}permission", "")
                component_name = elem.get(f"{ANDROID_NS}name", "unknown")
                if exported == "true" and not has_permission:
                    # Check if it has an intent filter (which makes export implied)
                    has_intent_filter = elem.find("intent-filter") is not None
                    if not has_intent_filter:
                        findings.append(Finding(
                            rule_id="AND-SEC-004",
                            severity="WARN",
                            message=f"Exported {tag} '{component_name}' has no permission protection",
                            fix=f"Add android:permission attribute to exported {tag} or set exported=false.",
                            reference="Android Security — Exported Components",
                        ))
                        break  # one finding per component type

    # AND-SEC-005 — ProGuard/R8 obfuscation not enabled in release
    has_release = bool(re.search(r'release\s*\{', gradle, re.DOTALL))
    proguard_enabled = bool(re.search(
        r'release\s*\{[^}]*(?:minifyEnabled\s*=?\s*true|isMinifyEnabled\s*=\s*true)', gradle, re.DOTALL
    ))
    if has_release and not proguard_enabled:
        findings.append(Finding(
            rule_id="AND-SEC-005",
            severity="WARN",
            message="ProGuard/R8 code obfuscation not enabled in release build",
            fix="Set minifyEnabled true in the release buildType in build.gradle.",
            reference="Android Security — Code Shrinking and Obfuscation",
        ))

    # AND-SEC-006 — Play Integrity API not integrated
    has_play_integrity = bool(re.search(
        r'com\.google\.android\.play:integrity|PlayIntegrity|IntegrityManager'
        r'|play_integrity'  # Flutter plugin
        r'|react-native-google-play-integrity',  # React Native
        gradle + source,
    ))
    has_safetynet = bool(re.search(r'SafetyNet|safetynet', gradle + source))
    if not has_play_integrity and not has_safetynet and gradle:
        findings.append(Finding(
            rule_id="AND-SEC-006",
            severity="INFO",
            message="Play Integrity API (or legacy SafetyNet) not integrated",
            fix="Integrate the Play Integrity API to verify app and device integrity.",
            reference="https://developer.android.com/google/play/integrity",
        ))

    return findings


# ---------------------------------------------------------------------------
# Hardcoded Secrets (AND-DATA-002)
# ---------------------------------------------------------------------------

_SECRET_PATTERNS = [
    (r'(?i)(?:api[_-]?key|apikey)\s*[:=]\s*["\'][A-Za-z0-9_\-]{16,}["\']', "API key"),
    (r'(?i)(?:secret|token|password|passwd)\s*[:=]\s*["\'][A-Za-z0-9_\-]{8,}["\']', "Secret/token"),
    (r'(?i)sk_(?:live|test)_[A-Za-z0-9]{24,}', "Stripe key"),
    (r'AIza[0-9A-Za-z_\-]{35}', "Google API key"),
    (r'(?i)aws[_-]?(?:access[_-]?key|secret)\s*[:=]\s*["\'][A-Za-z0-9/+=]{16,}["\']', "AWS credential"),
    (r'(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36,}', "GitHub token"),
    (r'github_pat_[A-Za-z0-9_]{20,}', "GitHub fine-grained token"),
]


def _check_secrets(source: str, gradle: str) -> List[Finding]:
    """Detect hardcoded secrets in source code and build files."""
    findings: List[Finding] = []
    found_types: List[str] = []
    combined = source + "\n" + gradle

    for pattern, label in _SECRET_PATTERNS:
        if re.search(pattern, combined):
            found_types.append(label)

    if found_types:
        findings.append(Finding(
            rule_id="AND-DATA-002",
            severity="FAIL",
            message=f"Hardcoded secrets detected: {', '.join(found_types)}",
            fix="Move secrets to gradle.properties (gitignored), BuildConfig fields from CI, or Android Keystore.",
            reference="OWASP Mobile Security — M9 Reverse Engineering",
        ))

    return findings


# ---------------------------------------------------------------------------
# Data Safety (AND-DATA-*)
# ---------------------------------------------------------------------------

def _check_data_safety(root: Path, gradle: str, source: str) -> List[Finding]:
    """Check for Google Play Data Safety section compliance."""
    findings: List[Finding] = []

    # AND-DATA-001 — Data collection markers
    data_collection_indicators = [
        # Analytics
        (r"FirebaseAnalytics|firebase-analytics|firebase_analytics|@react-native-firebase/analytics|AppCenter\.Analytics", "analytics data"),
        # Contacts
        (r"android\.permission\.READ_CONTACTS|contacts_service|react-native-contacts", "contacts"),
        # Location
        (r"android\.permission\.ACCESS_FINE_LOCATION|ACCESS_COARSE_LOCATION|geolocator|react-native-geolocation|Geolocation\.GetLocationAsync", "location data"),
        # Camera / Photos
        (r"android\.permission\.CAMERA|image_picker|camera\b|react-native-camera|expo-camera|MediaPicker", "photos/videos"),
        # App preferences / local storage
        (r"SharedPreferences|DataStore|shared_preferences|AsyncStorage|Preferences\.Set", "app preferences"),
    ]

    collected_data: List[str] = []
    combined = gradle + "\n" + source + "\n" + _load_manifest_text(root)
    for pattern, label in data_collection_indicators:
        if re.search(pattern, combined, re.IGNORECASE):
            collected_data.append(label)

    if collected_data:
        findings.append(Finding(
            rule_id="AND-DATA-001",
            severity="INFO",
            message=f"App appears to collect: {', '.join(collected_data)} — ensure Data Safety form is complete",
            fix="Complete your Google Play Data Safety section declaring all data types collected, shared, and retained.",
            reference="https://support.google.com/googleplay/android-developer/answer/10787469",
        ))

    return findings


# ---------------------------------------------------------------------------
# Play Store Policy (AND-POL-*)
# ---------------------------------------------------------------------------

_AD_SDK_PATTERNS = [
    r"com\.google\.android\.gms:play-services-ads",
    r"com\.facebook\.android:audience-network",
    r"com\.unity3d\.ads",
    r"com\.applovin",
]


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

    # AND-POL-001 — Ads SDK without disclosure
    has_ads_sdk = any(re.search(p, gradle) for p in _AD_SDK_PATTERNS)
    if has_ads_sdk:
        manifest_text = _load_manifest_text(root)
        has_ads_meta = "com.google.android.gms.ads.APPLICATION_ID" in manifest_text
        if not has_ads_meta:
            findings.append(Finding(
                rule_id="AND-POL-001",
                severity="FAIL",
                message="Ads SDK detected but no ad disclosure metadata in manifest",
                fix="Add ad disclosure metadata to AndroidManifest.xml and declare ads in Play Store listing.",
                reference="Google Play Ads Policy",
            ))

    # AND-POL-002 — Analytics without privacy policy
    analytics_patterns = [
        # Kotlin / Java / Gradle
        r"com\.google\.firebase:firebase-analytics",
        r"com\.google\.android\.gms:play-services-analytics",
        r"FirebaseAnalytics",
        # Flutter / Dart
        r"firebase_analytics", r"amplitude_flutter", r"mixpanel_flutter",
        # React Native
        r"@react-native-firebase/analytics", r"mixpanel-react-native",
        # .NET MAUI / Xamarin
        r"AppCenter\.Analytics",
    ]
    has_analytics = any(re.search(p, gradle + source) for p in analytics_patterns)
    if has_analytics:
        has_privacy = bool(re.search(r'privacy.policy|privacyPolicy|privacy_policy', source, re.IGNORECASE))
        if not has_privacy:
            findings.append(Finding(
                rule_id="AND-POL-002",
                severity="FAIL",
                message="Data collection detected but no privacy policy reference found",
                fix="Add a privacy policy URL to your Play Store listing and link it in your app.",
                reference="Google Play Developer Program Policies — User Data",
            ))

    # AND-POL-003 — Foreground service type not declared (Android 14+)
    manifest_text = _load_manifest_text(root)
    has_fg_service = bool(re.search(r'FOREGROUND_SERVICE', manifest_text))
    has_fg_service_type = bool(re.search(r'android:foregroundServiceType', manifest_text))
    if has_fg_service and not has_fg_service_type:
        findings.append(Finding(
            rule_id="AND-POL-003",
            severity="FAIL",
            message="Foreground service declared without foregroundServiceType (required on Android 14+)",
            fix="Add android:foregroundServiceType attribute to <service> elements using foreground services.",
            reference="https://developer.android.com/about/versions/14/changes/fgs-types-required",
        ))

    # AND-POL-004 — Legacy external storage access (should use Photo Picker)
    legacy_file_patterns = [
        r"READ_EXTERNAL_STORAGE",
        r"WRITE_EXTERNAL_STORAGE",
        r"MANAGE_EXTERNAL_STORAGE",
    ]
    has_legacy_storage = any(re.search(p, manifest_text) for p in legacy_file_patterns)
    has_photo_picker = bool(re.search(
        # Kotlin / Java
        r'PhotoPicker|ActivityResultContracts\.PickVisualMedia|ACTION_PICK_IMAGES'
        # Flutter / Dart
        r'|image_picker|file_picker'
        # React Native
        r'|react-native-image-picker|expo-image-picker'
        # .NET MAUI / Xamarin
        r'|MediaPicker|FilePicker\.PickAsync',
        source,
    ))
    if has_legacy_storage and not has_photo_picker:
        findings.append(Finding(
            rule_id="AND-POL-004",
            severity="INFO",
            message="Legacy external storage permissions used — consider migrating to Photo Picker API",
            fix="Use the Photo Picker API (ActivityResultContracts.PickVisualMedia) instead of broad storage permissions.",
            reference="https://developer.android.com/training/data-storage/shared/photopicker",
        ))

    return findings
