"""
Module d'analyse des technologies web modernes pour Web Sentinel.
Détecte les frameworks SPA, PWA, et leurs configurations de sécurité.
"""

from __future__ import annotations

import json
import logging
import re
from typing import Dict, Iterable, List, Optional, Set
from urllib.parse import urljoin, urlparse

import requests

from ..model import Finding, ScanRequest

LOGGER = logging.getLogger("web_sentinel.checks.modern_web")

# Signatures de frameworks JavaScript modernes
FRAMEWORK_SIGNATURES = {
    'react': [
        r'react\.development\.js',
        r'react\.production\.min\.js',
        r'data-react',
        r'__REACT_DEVTOOLS_GLOBAL_HOOK__',
    ],
    'vue': [
        r'vue\.js',
        r'vue\.min\.js',
        r'data-v-',
        r'__VUE__',
    ],
    'angular': [
        r'angular\.js',
        r'angular\.min\.js',
        r'ng-',
        r'data-ng-',
    ],
    'svelte': [
        r'svelte',
        r'_svelte',
    ],
    'next.js': [
        r'_next/',
        r'__NEXT_DATA__',
        r'next\.config',
    ],
    'nuxt': [
        r'_nuxt/',
        r'__NUXT__',
    ],
    'gatsby': [
        r'___gatsby',
        r'gatsby-',
    ]
}

# Fichiers de configuration à vérifier
CONFIG_FILES = [
    'manifest.json',        # PWA manifest
    'sw.js',               # Service Worker
    'service-worker.js',   # Service Worker alternatif
    'workbox-*.js',        # Workbox
    '.well-known/assetlinks.json',  # Android App Links
    '.well-known/apple-app-site-association',  # iOS Universal Links
    'browserconfig.xml',   # Microsoft tiles
    'humans.txt',          # Informations développeurs
]

# Headers spécifiques aux PWAs et SPAs
MODERN_WEB_HEADERS = [
    'X-Powered-By',
    'X-Framework',
    'X-Generator',
    'Server-Timing',
    'X-Edge-Cache',
    'X-Vercel-Cache',
    'X-Netlify-Cache',
    'CF-Cache-Status',     # Cloudflare
]


def evaluate_modern_web_technologies(request: ScanRequest) -> Iterable[Finding]:
    """
    Analyse les technologies web modernes et leurs configurations de sécurité.
    """
    findings: List[Finding] = []
    base_url = f"https://{request.domain}"
    
    # Analyse de la page principale
    try:
        response = requests.get(
            base_url,
            headers={"User-Agent": request.user_agent},
            timeout=request.timeout,
            allow_redirects=True
        )
        
        findings.extend(_detect_frameworks(response))
        findings.extend(_analyze_modern_headers(response))
        findings.extend(_check_spa_security(response))
        
    except requests.RequestException as exc:
        findings.append(
            Finding(
                check="modern-web",
                title="Cannot analyze modern web technologies",
                severity="medium",
                description=f"Failed to fetch main page for analysis: {exc}",
                remediation="Verify site accessibility and try again.",
                evidence=f"URL: {base_url}"
            ,
                i18n_key="modern.analysis_failed",
            )
        )
        return findings
    
    # Analyse des fichiers de configuration
    findings.extend(_analyze_config_files(request))
    
    # Détection PWA
    findings.extend(_analyze_pwa_configuration(request))
    
    return findings


def _detect_frameworks(response: requests.Response) -> List[Finding]:
    """
    Détecte les frameworks JavaScript utilisés.
    """
    findings: List[Finding] = []
    content = response.text.lower()
    detected_frameworks = []
    
    for framework, signatures in FRAMEWORK_SIGNATURES.items():
        for signature in signatures:
            if re.search(signature, content, re.IGNORECASE):
                detected_frameworks.append(framework)
                break
    
    if detected_frameworks:
        frameworks_str = ', '.join(detected_frameworks)
        findings.append(
            Finding(
                check="modern-web",
                title="JavaScript frameworks detected",
                severity="info",
                description=f"Detected frameworks: {frameworks_str}",
                remediation="Ensure frameworks are up-to-date and securely configured.",
                evidence=f"Frameworks: {frameworks_str}",
                impact="Framework-specific security considerations may apply."
            ,
                i18n_key="modern.frameworks_detected",
            )
        )
        
        # Vérifications spécifiques aux frameworks
        for framework in detected_frameworks:
            findings.extend(_check_framework_security(response, framework))
    
    return findings


def _check_framework_security(response: requests.Response, framework: str) -> List[Finding]:
    """
    Vérifications de sécurité spécifiques aux frameworks.
    """
    findings: List[Finding] = []
    content = response.text
    
    if framework == 'react':
        # Vérification de React en mode développement
        if 'react.development.js' in content.lower():
            findings.append(
                Finding(
                    check="modern-web",
                    title="React development build detected",
                    severity="medium",
                    description="React is running in development mode in production",
                    remediation="Use production build of React (react.production.min.js,
                i18n_key="modern.react_dev_build",
            ).",
                    evidence="react.development.js found in page",
                    impact="Development builds expose debugging information and have worse performance."
                )
            )
    
    elif framework == 'angular':
        # Vérification d'Angular en mode debug
        if 'ng.probe' in content or 'enableProdMode' not in content:
            findings.append(
                Finding(
                    check="modern-web",
                    title="Angular development mode detected",
                    severity="medium",
                    description="Angular may be running in development mode",
                    remediation="Enable production mode with enableProdMode(,
                i18n_key="modern.angular_dev_mode",
            ) in main.ts.",
                    evidence="Development mode indicators found",
                    impact="Development mode exposes debugging tools and reduces performance."
                )
            )
    
    elif framework in ['next.js', 'nuxt']:
        # Vérification des variables d'environnement exposées
        env_pattern = r'(process\.env\.|__ENV__|NODE_ENV)'
        if re.search(env_pattern, content, re.IGNORECASE):
            findings.append(
                Finding(
                    check="modern-web",
                    title="Environment variables exposed in client",
                    severity="medium",
                    description=f"Environment variables may be exposed in {framework} client bundle",
                    remediation="Ensure only NEXT_PUBLIC_ or NUXT_ENV_ prefixed variables are exposed to client.",
                    evidence="Environment variable references found in client code",
                    impact="Sensitive environment variables may be exposed to users."
                ,
                i18n_key="modern.env_vars_exposed",
            )
            )
    
    return findings


def _analyze_modern_headers(response: requests.Response) -> List[Finding]:
    """
    Analyse les headers spécifiques aux technologies modernes.
    """
    findings: List[Finding] = []
    
    # Détection de plateformes de déploiement
    deployment_headers = {
        'x-vercel-cache': 'Vercel',
        'x-netlify-cache': 'Netlify',
        'cf-cache-status': 'Cloudflare',
        'x-github-request-id': 'GitHub Pages',
        'x-served-by': 'Fastly/CDN',
    }
    
    detected_platforms = []
    for header, platform in deployment_headers.items():
        if header.lower() in [h.lower() for h in response.headers.keys()]:
            detected_platforms.append(platform)
    
    if detected_platforms:
        platforms_str = ', '.join(detected_platforms)
        findings.append(
            Finding(
                check="modern-web",
                title="Deployment platform detected",
                severity="info",
                description=f"Site appears to be deployed on: {platforms_str}",
                remediation="Ensure platform security settings are properly configured.",
                evidence=f"Platform headers: {platforms_str}",
                impact="Platform-specific security considerations may apply."
            ,
                i18n_key="modern.deployment_platform",
            )
        )
    
    # Vérification des headers de timing
    server_timing = response.headers.get('Server-Timing')
    if server_timing and 'dur=' in server_timing:
        # Vérifier si des informations sensibles sont exposées
        if any(keyword in server_timing.lower() for keyword in ['db', 'database', 'sql', 'cache', 'redis']):
            findings.append(
                Finding(
                    check="modern-web",
                    title="Sensitive information in Server-Timing header",
                    severity="low",
                    description="Server-Timing header may expose internal system information",
                    remediation="Review Server-Timing header content for sensitive information.",
                    evidence=f"Server-Timing: {server_timing}",
                    impact="Internal timing information may aid in reconnaissance."
                ,
                i18n_key="modern.server_timing_sensitive",
            )
            )
    
    return findings


def _check_spa_security(response: requests.Response) -> List[Finding]:
    """
    Vérifications de sécurité spécifiques aux Single Page Applications.
    """
    findings: List[Finding] = []
    content = response.text
    
    # Vérification des tokens/clés exposés dans le code client
    sensitive_patterns = {
        'api_key': r'["\']?api[_-]?key["\']?\s*[:=]\s*["\'][^"\']{16,}["\']',
        'secret': r'["\']?secret["\']?\s*[:=]\s*["\'][^"\']{16,}["\']',
        'token': r'["\']?token["\']?\s*[:=]\s*["\'][^"\']{20,}["\']',
        'jwt': r'ey[A-Za-z0-9_-]{10,}\.ey[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}',
    }
    
    for pattern_name, pattern in sensitive_patterns.items():
        matches = re.findall(pattern, content, re.IGNORECASE)
        if matches:
            findings.append(
                Finding(
                    check="modern-web",
                    title=f"Potential {pattern_name.replace('_', ' ')} exposed in client code",
                    severity="high",
                    description=f"Client-side code may contain {pattern_name.replace('_', ' ')} information",
                    remediation="Move sensitive credentials to server-side code and use environment variables.",
                    evidence=f"Found {len(matches)} potential matches",
                    impact="Exposed credentials can be extracted by anyone viewing the page source."
                )
            )
    
    # Vérification des URLs d'API hardcodées
    api_url_pattern = r'https?://[^"\'\s]+/(api|rest|graphql)/[^"\'\s]+'
    api_urls = re.findall(api_url_pattern, content, re.IGNORECASE)
    if api_urls:
        # Filtrer les URLs internes vs externes
        current_domain = urlparse(response.url).netloc
        external_apis = [url for url in api_urls if current_domain not in url]
        
        if external_apis:
            findings.append(
                Finding(
                    check="modern-web",
                    title="External API URLs hardcoded in client",
                    severity="medium",
                    description=f"Found {len(external_apis,
                i18n_key="modern.external_apis_hardcoded",
            )} external API URLs in client code",
                    remediation="Use environment variables or server-side proxy for external API calls.",
                    evidence=f"External APIs: {external_apis[:3]}{'...' if len(external_apis) > 3 else ''}",
                    impact="Hardcoded API URLs may expose backend architecture and endpoints."
                )
            )
    
    return findings


def _analyze_config_files(request: ScanRequest) -> List[Finding]:
    """
    Analyse les fichiers de configuration modernes.
    """
    findings: List[Finding] = []
    base_url = f"https://{request.domain}"
    
    for config_file in CONFIG_FILES:
        url = urljoin(base_url, config_file)
        try:
            response = requests.get(
                url,
                headers={"User-Agent": request.user_agent},
                timeout=request.timeout
            )
            
            if response.status_code == 200:
                findings.extend(_analyze_config_content(response, config_file))
                
        except requests.RequestException:
            continue
    
    return findings


def _analyze_config_content(response: requests.Response, filename: str) -> List[Finding]:
    """
    Analyse le contenu d'un fichier de configuration.
    """
    findings: List[Finding] = []
    
    if filename == 'manifest.json':
        findings.extend(_analyze_pwa_manifest(response))
    elif filename.endswith('.js') and 'sw' in filename:
        findings.extend(_analyze_service_worker(response))
    elif 'assetlinks.json' in filename:
        findings.extend(_analyze_app_links(response))
    
    return findings


def _analyze_pwa_configuration(request: ScanRequest) -> List[Finding]:
    """
    Analyse spécifique de la configuration PWA.
    """
    findings: List[Finding] = []
    base_url = f"https://{request.domain}"
    
    # Vérification du manifest PWA
    manifest_url = urljoin(base_url, 'manifest.json')
    try:
        response = requests.get(
            manifest_url,
            headers={"User-Agent": request.user_agent},
            timeout=request.timeout
        )
        
        if response.status_code == 200:
            findings.append(
                Finding(
                    check="modern-web",
                    title="PWA manifest detected",
                    severity="info",
                    description="Progressive Web App manifest found",
                    remediation="Ensure PWA security best practices are followed.",
                    evidence=f"Manifest available at: {manifest_url}",
                    impact="PWA capabilities require additional security considerations."
                ,
                i18n_key="modern.pwa_detected",
            )
            )
            findings.extend(_analyze_pwa_manifest(response))
        
    except requests.RequestException:
        pass
    
    return findings


def _analyze_pwa_manifest(response: requests.Response) -> List[Finding]:
    """
    Analyse du fichier manifest PWA.
    """
    findings: List[Finding] = []
    
    try:
        manifest = response.json()
        
        # Vérification des permissions dangereuses
        dangerous_permissions = [
            'geolocation', 'notifications', 'camera', 'microphone',
            'background-sync', 'push-messaging'
        ]
        
        # Dans le manifest, chercher les scope et permissions
        start_url = manifest.get('start_url', '')
        scope = manifest.get('scope', '')
        
        # Vérification du scope trop permissif
        if scope == '/' or not scope:
            findings.append(
                Finding(
                    check="modern-web",
                    title="PWA scope too permissive",
                    severity="medium",
                    description="PWA scope allows access to entire domain",
                    remediation="Restrict PWA scope to specific application paths.",
                    evidence=f"Scope: '{scope}'",
                    impact="Broad scope may allow PWA to intercept unintended resources."
                ,
                i18n_key="modern.pwa_scope_permissive",
            )
            )
        
        # Vérification des icônes avec protocols non-sécurisés
        icons = manifest.get('icons', [])
        for icon in icons:
            src = icon.get('src', '')
            if src.startswith('http://'):
                findings.append(
                    Finding(
                        check="modern-web",
                        title="PWA icon served over HTTP",
                        severity="low",
                        description="PWA icon is served over insecure HTTP",
                        remediation="Serve all PWA resources over HTTPS.",
                        evidence=f"Icon src: {src}",
                        impact="Insecure resources may be manipulated in transit."
                    ,
                i18n_key="modern.pwa_insecure_icon",
            )
                )
                break
        
    except (json.JSONDecodeError, ValueError):
        findings.append(
            Finding(
                check="modern-web",
                title="Invalid PWA manifest format",
                severity="low",
                description="PWA manifest file contains invalid JSON",
                remediation="Fix JSON syntax errors in manifest.json.",
                evidence="JSON parsing failed",
                impact="Invalid manifest may prevent PWA installation."
            ,
                i18n_key="modern.pwa_invalid_manifest",
            )
        )
    
    return findings


def _analyze_service_worker(response: requests.Response) -> List[Finding]:
    """
    Analyse du Service Worker.
    """
    findings: List[Finding] = []
    content = response.text
    
    # Vérification des caches non sécurisés
    if 'http://' in content:
        findings.append(
            Finding(
                check="modern-web",
                title="Service Worker caches HTTP resources",
                severity="medium",
                description="Service Worker may cache resources served over HTTP",
                remediation="Ensure all cached resources are served over HTTPS.",
                evidence="HTTP URLs found in Service Worker",
                impact="Cached insecure resources may persist and be served to users."
            ,
                i18n_key="modern.sw_insecure_resources",
            )
        )
    
    # Vérification des stratégies de cache trop permissives
    if 'staleWhileRevalidate' in content and 'fetch(' in content:
        findings.append(
            Finding(
                check="modern-web",
                title="Potentially aggressive caching strategy",
                severity="low",
                description="Service Worker uses stale-while-revalidate caching",
                remediation="Review caching strategy for sensitive data and authentication state.",
                evidence="staleWhileRevalidate strategy detected",
                impact="Aggressive caching may serve stale authentication or sensitive data."
            ,
                i18n_key="modern.sw_aggressive_caching",
            )
        )
    
    return findings


def _analyze_app_links(response: requests.Response) -> List[Finding]:
    """
    Analyse des liens d'application (Android App Links, iOS Universal Links).
    """
    findings: List[Finding] = []
    
    try:
        if 'assetlinks.json' in response.url:
            # Android App Links
            links_data = response.json()
            if isinstance(links_data, list) and links_data:
                findings.append(
                    Finding(
                        check="modern-web",
                        title="Android App Links configured",
                        severity="info",
                        description="Android App Links configuration detected",
                        remediation="Ensure app link targets are properly validated in the mobile app.",
                        evidence=f"Found {len(links_data,
                i18n_key="modern.android_app_links",
            )} app link configurations",
                        impact="App links create additional attack surface between web and mobile app."
                    )
                )
        
        elif 'apple-app-site-association' in response.url:
            # iOS Universal Links
            association_data = response.json()
            if 'applinks' in association_data:
                findings.append(
                    Finding(
                        check="modern-web",
                        title="iOS Universal Links configured",
                        severity="info",
                        description="iOS Universal Links configuration detected",
                        remediation="Ensure universal link handling is properly validated in the iOS app.",
                        evidence="Universal Links configuration found",
                        impact="Universal links create additional attack surface between web and mobile app."
                    ,
                i18n_key="modern.ios_universal_links",
            )
                )
                
    except (json.JSONDecodeError, ValueError):
        pass
    
    return findings