"""
Module d'analyse des configurations backend pour Web Sentinel.
Détecte les configurations de sécurité non visibles dans les réponses HTTP.
"""

from __future__ import annotations

import json
import logging
import os
import re
from pathlib import Path
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.backend_config")

# Configuration files patterns for different frameworks
CONFIG_PATTERNS = {
    'express': [
        'package.json', 'app.js', 'server.js', 'index.js',
        'config/*.js', 'config/*.json', 'middleware/*.js'
    ],
    'django': [
        'settings.py', 'settings/*.py', 'requirements.txt',
        'wsgi.py', 'asgi.py', 'urls.py'
    ],
    'spring': [
        'application.properties', 'application.yml', 'application.yaml',
        'pom.xml', 'build.gradle', 'src/main/resources/application*'
    ],
    'asp_net': [
        'web.config', 'appsettings.json', 'appsettings.*.json',
        'Startup.cs', 'Program.cs', '*.csproj'
    ],
    'laravel': [
        'config/app.php', 'config/cors.php', '.env.example',
        'composer.json', 'config/*.php'
    ]
}

# Security configuration indicators
SECURITY_INDICATORS = {
    'cors': {
        'express': ['cors()', 'Access-Control-Allow-Origin', 'app.use(cors'],
        'django': ['CORS_ALLOWED_ORIGINS', 'django-cors-headers', 'CORS_ALLOW_ALL_ORIGINS'],
        'spring': ['@CrossOrigin', 'CorsConfiguration', 'allowedOrigins'],
        'asp_net': ['UseCors', 'WithOrigins', 'AllowAnyOrigin'],
        'laravel': ['cors.php', 'allowed_origins', 'HandleCors']
    },
    'csrf': {
        'express': ['csurf', 'csrf-protection', 'app.use(csrf'],
        'django': ['CsrfViewMiddleware', 'csrf_token', 'CSRF_COOKIE_SECURE'],
        'spring': ['@EnableWebSecurity', 'CsrfConfigurer', 'csrf()'],
        'asp_net': ['ValidateAntiForgeryToken', 'AntiforgeryOptions'],
        'laravel': ['VerifyCsrfToken', 'csrf_token()', '_token']
    },
    'session_security': {
        'express': ['express-session', 'secure: true', 'httpOnly: true'],
        'django': ['SESSION_COOKIE_SECURE', 'SESSION_COOKIE_HTTPONLY'],
        'spring': ['HttpSecurity', 'sessionManagement()', 'sessionCreationPolicy'],
        'asp_net': ['CookieSecurePolicy', 'HttpOnlyCookies'],
        'laravel': ['SESSION_SECURE_COOKIE', 'SESSION_HTTP_ONLY']
    },
    'input_validation': {
        'express': ['express-validator', 'joi', 'yup', 'validator'],
        'django': ['ModelForm', 'serializers.Serializer', 'forms.Form'],
        'spring': ['@Valid', '@Validated', 'Validator', 'BindingResult'],
        'asp_net': ['ModelState.IsValid', '[Required]', 'DataAnnotations'],
        'laravel': ['Request::validate', 'Validator::make', 'FormRequest']
    }
}


def evaluate_backend_configuration(request: ScanRequest) -> Iterable[Finding]:
    """
    Analyse les configurations de sécurité backend via les endpoints d'information
    et les patterns de configuration accessibles publiquement.
    
    Note: Cette fonction respecte les limites éthiques et ne tente d'accéder
    qu'aux informations publiquement disponibles ou via des endpoints légitimes.
    """
    findings: List[Finding] = []
    
    if not request.allow_invasive:
        findings.append(
            Finding(
                check="backend-config",
                title="Backend analysis requires invasive mode",
                severity="info",
                description="Backend configuration analysis requires --allow-invasive flag for enhanced detection.",
                remediation="Run scan with --allow-invasive if you have legitimate access to the application.",
                impact="Limited to HTTP response analysis only.",
                i18n_key="backend.analysis_requires_invasive",
            )
        )
        return findings
    
    # Détection du framework via les headers et patterns de réponse
    framework = _detect_framework(request)
    findings.extend(_analyze_public_endpoints(request, framework))
    findings.extend(_analyze_error_pages(request))
    findings.extend(_analyze_development_indicators(request))
    
    return findings


def _detect_framework(request: ScanRequest) -> Optional[str]:
    """
    Détecte le framework backend via l'analyse des headers et patterns.
    """
    url = f"https://{request.domain}"
    
    try:
        response = requests.get(
            url,
            headers={"User-Agent": request.user_agent},
            timeout=request.timeout,
            allow_redirects=True
        )
        
        # Analyse des headers révélateurs
        server_header = response.headers.get('Server', '').lower()
        powered_by = response.headers.get('X-Powered-By', '').lower()
        
        if 'express' in powered_by or 'node' in server_header:
            return 'express'
        elif 'django' in server_header or 'python' in server_header:
            return 'django'
        elif 'tomcat' in server_header or 'java' in server_header:
            return 'spring'
        elif 'iis' in server_header or 'asp.net' in powered_by:
            return 'asp_net'
        elif 'apache' in server_header and 'php' in powered_by:
            return 'laravel'
            
    except requests.RequestException:
        pass
    
    return None


def _analyze_public_endpoints(request: ScanRequest, framework: Optional[str]) -> List[Finding]:
    """
    Analyse les endpoints publics qui peuvent révéler des informations de configuration.
    """
    findings: List[Finding] = []
    base_url = f"https://{request.domain}"
    
    # Endpoints communs d'information
    info_endpoints = [
        '/.well-known/security.txt',
        '/robots.txt',
        '/sitemap.xml',
        '/health',
        '/status',
        '/info',
        '/actuator/health',  # Spring Boot
        '/api/health',
        '/.env',  # Fichier de configuration (ne devrait pas être accessible)
        '/package.json',  # Node.js (ne devrait pas être accessible)
    ]
    
    for endpoint in info_endpoints:
        url = urljoin(base_url, endpoint)
        try:
            response = requests.get(
                url,
                headers={"User-Agent": request.user_agent},
                timeout=request.timeout
            )
            
            if response.status_code == 200:
                content = response.text.lower()
                
                # Analyse du contenu pour détecter des configurations de sécurité
                if endpoint == '/.well-known/security.txt':
                    findings.append(
                        Finding(
                            check="backend-config",
                            title="Security.txt found",
                            severity="info",
                            description=f"Security policy information available at {endpoint}",
                            remediation="Review security.txt for contact information and policies.",
                            evidence=f"Accessible at: {url}",
                            i18n_key="backend.security_txt_found",
                            i18n_params={"endpoint": endpoint, "url": url}
                        )
                    )
                
                elif endpoint in ['/.env', '/package.json'] and response.status_code == 200:
                    findings.append(
                        Finding(
                            check="backend-config",
                            title="Sensitive configuration file exposed",
                            severity="high",
                            description=f"Configuration file {endpoint} is publicly accessible",
                            remediation="Restrict access to configuration files via web server configuration.",
                            evidence=f"Accessible at: {url}",
                            impact="Configuration files may contain sensitive information.",
                            i18n_key="backend.config_file_exposed",
                            i18n_params={"endpoint": endpoint, "url": url}
                        )
                    )
                
                elif '/actuator/' in endpoint:
                    _analyze_actuator_endpoint(findings, url, content)
                    
        except requests.RequestException:
            continue
    
    return findings


def _analyze_actuator_endpoint(findings: List[Finding], url: str, content: str) -> None:
    """
    Analyse spécifique des endpoints Spring Boot Actuator.
    """
    if 'status' in content and 'up' in content:
        # Endpoint de santé détecté
        if '"diskSpace"' in content or '"db"' in content:
            findings.append(
                Finding(
                    check="backend-config",
                    title="Detailed health endpoint exposed",
                    severity="medium",
                    description="Spring Boot Actuator health endpoint reveals internal system details",
                    remediation="Configure management.endpoint.health.show-details=when-authorized",
                    evidence=f"Detailed health info at: {url}",
                    impact="Internal system information may aid in reconnaissance.",
                    i18n_key="backend.actuator_details_exposed",
                    i18n_params={"url": url}
                )
            )


def _analyze_error_pages(request: ScanRequest) -> List[Finding]:
    """
    Analyse les pages d'erreur pour détecter des fuites d'information sur la configuration.
    """
    findings: List[Finding] = []
    base_url = f"https://{request.domain}"
    
    # Test d'endpoints inexistants pour déclencher des pages d'erreur
    test_endpoints = [
        '/nonexistent-page-12345',
        '/admin/nonexistent',
        '/api/nonexistent',
    ]
    
    for endpoint in test_endpoints:
        url = urljoin(base_url, endpoint)
        try:
            response = requests.get(
                url,
                headers={"User-Agent": request.user_agent},
                timeout=request.timeout
            )
            
            if response.status_code in [404, 500]:
                content = response.text.lower()
                
                # Détection d'informations sensibles dans les erreurs
                if any(pattern in content for pattern in ['stack trace', 'exception', 'debug']):
                    findings.append(
                        Finding(
                            check="backend-config",
                            title="Verbose error pages detected",
                            severity="medium",
                            description="Error pages reveal stack traces or debug information",
                            remediation="Configure custom error pages and disable debug mode in production.",
                            evidence=f"Verbose error at: {url}",
                            impact="Debug information may reveal application structure and vulnerabilities."
                        ,
                i18n_key="backend.verbose_error_pages",
            )
                    )
                    break  # Une seule détection suffit
                    
        except requests.RequestException:
            continue
    
    return findings


def _analyze_development_indicators(request: ScanRequest) -> List[Finding]:
    """
    Détecte les indicateurs suggérant un environnement de développement en production.
    """
    findings: List[Finding] = []
    base_url = f"https://{request.domain}"
    
    # Endpoints typiques des environnements de développement
    dev_endpoints = [
        '/debug',
        '/test',
        '/dev',
        '/swagger-ui',
        '/api-docs',
        '/phpmyadmin',
        '/adminer',
        '/__debug__',
        '/telescope',  # Laravel Telescope
    ]
    
    for endpoint in dev_endpoints:
        url = urljoin(base_url, endpoint)
        try:
            response = requests.get(
                url,
                headers={"User-Agent": request.user_agent},
                timeout=request.timeout
            )
            
            if response.status_code == 200:
                findings.append(
                    Finding(
                        check="backend-config",
                        title="Development tool accessible in production",
                        severity="high",
                        description=f"Development endpoint {endpoint} is accessible in production",
                        remediation="Disable development tools and debug interfaces in production environment.",
                        evidence=f"Accessible at: {url}",
                        impact="Development tools may expose sensitive application data and functionality."
                    ,
                i18n_key="backend.dev_tool_accessible",
            )
                )
                
        except requests.RequestException:
            continue
    
    return findings


def _check_cors_configuration(request: ScanRequest) -> List[Finding]:
    """
    Teste la configuration CORS via des requêtes OPTIONS.
    """
    findings: List[Finding] = []
    base_url = f"https://{request.domain}"
    
    # Test CORS avec différentes origines
    test_origins = [
        "https://evil.example.com",
        "null",
        "https://malicious-site.com"
    ]
    
    for origin in test_origins:
        try:
            response = requests.options(
                base_url,
                headers={
                    "Origin": origin,
                    "Access-Control-Request-Method": "POST",
                    "User-Agent": request.user_agent
                },
                timeout=request.timeout
            )
            
            cors_origin = response.headers.get('Access-Control-Allow-Origin')
            if cors_origin == '*' or cors_origin == origin:
                findings.append(
                    Finding(
                        check="backend-config",
                        title="Permissive CORS configuration detected",
                        severity="high",
                        description=f"CORS allows requests from {origin}",
                        remediation="Configure CORS to allow only trusted origins.",
                        evidence=f"Access-Control-Allow-Origin: {cors_origin}",
                        impact="Permissive CORS may enable cross-origin attacks."
                    ,
                i18n_key="backend.permissive_cors",
            )
                )
                break  # Une détection suffit
                
        except requests.RequestException:
            continue
    
    return findings