from __future__ import annotations

"""Enterprise Security, OWASP Defense-in-Depth, and WAF Anti-Hacker Middleware for DSCons ERP."""

import logging
import re
from urllib.parse import unquote

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import JSONResponse, Response

logger = logging.getLogger("dscons.security.waf")

# Các mẫu tấn công độc hại phổ biến (SQLi, XSS, Path Traversal, Command Injection)
MALICIOUS_PATTERNS = [
    (
        r"(?i)(union\s+select|select\s+.*\s+from\s+pg_|insert\s+into\s+.*values|drop\s+table|delete\s+from\s+.*where)",
        "SQL_INJECTION",
    ),
    (r"(?i)('\s*or\s*'1'\s*=\s*'1|--\s*$|;\s*drop\s+)", "SQL_BYPASS_INJECTION"),
    (
        r"(?i)(<script[^>]*>|javascript:|onerror\s*=|onload\s*=|alert\(|<iframe)",
        "CROSS_SITE_SCRIPTING",
    ),
    (r"(\.\./\.\./|\.\.\\\.\.\\|/etc/passwd|windows/system32)", "PATH_TRAVERSAL"),
    (
        r"(?i)(\|\|\s*bash|;\s*rm\s+-rf|;\s*powershell|\$\(whoami\))",
        "COMMAND_INJECTION",
    ),
]


class EnterpriseSecurityMiddleware(BaseHTTPMiddleware):
    """Adds enterprise-grade HTTP security headers and performs basic request inspection."""

    async def dispatch(self, request: Request, call_next) -> Response:
        # 1. WAF Request Path & Query Sanitization
        raw_url = str(request.url)
        decoded_url = unquote(raw_url)

        for pattern, threat_type in MALICIOUS_PATTERNS:
            if re.search(pattern, decoded_url):
                logger.warning(
                    "[SECURITY WAF BLOCK] Threat: %s | Client IP: %s | URL: %s",
                    threat_type,
                    request.client.host if request.client else "unknown",
                    request.url.path,
                )
                return JSONResponse(
                    status_code=400,
                    content={
                        "status": "error",
                        "error_code": "SECURITY_THREAT_BLOCKED",
                        "message": f"Yêu cầu chứa mẫu độc hại bị chặn bởi DSCons Security Shield ({threat_type}).",
                    },
                )

        # 2. Process Next Handler
        response: Response = await call_next(request)

        # 3. Inject OWASP Security & Hardening Headers
        response.headers["X-Frame-Options"] = "SAMEORIGIN"
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
        response.headers["Permissions-Policy"] = (
            "geolocation=(self), microphone=(), camera=()"
        )
        response.headers["X-Permitted-Cross-Domain-Policies"] = "none"
        response.headers["Strict-Transport-Security"] = (
            "max-age=31536000; includeSubDomains; preload"
        )
        response.headers["Content-Security-Policy"] = (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline' 'unsafe-eval' https://cdn.tailwindcss.com https://cdn.jsdelivr.net blob:; "
            "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.tailwindcss.com https://cdn.jsdelivr.net; "
            "font-src 'self' https://fonts.gstatic.com data:; "
            "img-src 'self' data: https: blob: https://fastapi.tiangolo.com https://cdn.jsdelivr.net; "
            "media-src 'self' blob: data:; "
            "connect-src 'self' https: ws: wss:; "
            "frame-ancestors 'self'; "
            "base-uri 'self'; "
            "form-action 'self';"
        )

        # Zero-cache policy for dynamic dashboards and sensitive ERP routes
        if request.url.path.startswith("/v1/") or request.url.path.startswith(
            "/dashboard/"
        ):
            response.headers["Cache-Control"] = (
                "no-store, no-cache, must-revalidate, max-age=0"
            )
            response.headers["Pragma"] = "no-cache"

        return response

from collections import defaultdict, deque
import time

class SimpleGlobalRateLimitMiddleware(BaseHTTPMiddleware):
    """Global Rate Limiter (Sliding Window Memory) to prevent DDoS/Bruteforce."""
    def __init__(self, app, max_requests: int = 60, window_seconds: int = 60):
        super().__init__(app)
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self.ip_records = defaultdict(deque)

    async def dispatch(self, request: Request, call_next):
        # Bypass static files
        if request.url.path.startswith("/static") or request.url.path.startswith("/storage"):
            return await call_next(request)
            
        ip = request.client.host if request.client else "unknown"
        now = time.time()
        
        history = self.ip_records[ip]
        while history and history[0] < now - self.window_seconds:
            history.popleft()
            
        if len(history) >= self.max_requests:
            logger.warning(f"[RATE LIMIT] Blocked IP {ip} - Exceeded {self.max_requests} req / {self.window_seconds}s")
            return JSONResponse(
                status_code=429, 
                content={
                    "status": "error",
                    "error_code": "TOO_MANY_REQUESTS", 
                    "message": f"Hệ thống đang chịu tải cao. Vui lòng thử lại sau {self.window_seconds} giây."
                }
            )
            
        history.append(now)
        return await call_next(request)

from starlette.responses import RedirectResponse

class LegacyRedirectMiddleware(BaseHTTPMiddleware):
    """Redirects old URLs to the new /v1/erp/... standard and routes erp subdomain."""
    async def dispatch(self, request: Request, call_next):
        path = request.url.path
        host = request.headers.get("host", "").lower()

        # Handle ERP subdomain root navigation
        if host.startswith("erp.") and path in ("/", "/landing", "/landing.html"):
            return RedirectResponse(url="/v1/erp/dashboard", status_code=302)

        # Exact root paths that need redirecting
        legacy_prefixes = (
            "/dashboard", "/login", "/takeoff", "/drawing-takeoff", "/material-prices",
            "/material-price-comparison", "/state-prices", "/zalo-crm", "/documents-dashboard",
            "/documents", "/projects", "/wbs", "/finance", "/war-room", "/employees", "/users",
            "/equipment", "/invoices", "/banking", "/partners", "/vendors", "/customers",
            "/dhs", "/agent-models", "/site-diary", "/site-pwa", "/settings"
        )
        
        if path.startswith(legacy_prefixes):
            query = f"?{request.url.query}" if request.url.query else ""
            return RedirectResponse(url=f"/v1/erp{path}{query}", status_code=301)
            
        return await call_next(request)
