"""
API Security Hardening — Defense against dev tools / network tab snooping.

Middleware and utilities that harden the Brynq API server against:
  - Cross-origin abuse (strict CORS)
  - Request flooding (per-IP rate limiting)
  - Bot/scraper probing (User-Agent + header validation)
  - Information leakage (security headers, version stripping)

Usage:
    from brynq.api_security import apply_security_hardening
    apply_security_hardening(app)
"""

import time
from collections import defaultdict
from typing import Callable

from fastapi import FastAPI, Request, Response
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware


# ── Configuration ────────────────────────────────────────────────────────

# Allowed origins — only production domains and local dev
ALLOWED_ORIGINS = [
    "https://brynq.ai",
    "https://brynq-ai.pages.dev",
    "http://localhost:8080",
    "http://localhost:9999",
]

# Rate limit: requests per window
RATE_LIMIT_MAX_REQUESTS = 60
RATE_LIMIT_WINDOW_SECONDS = 60

# User-Agent strings that indicate automated scraping / dev tools probing
BLOCKED_USER_AGENTS = [
    "python-requests",  # default requests lib UA — real integrations should set a proper UA
    "curl",
    "wget",
    "httpie",
    "postman",
    "insomnia",
]

# Suspicious headers that indicate proxy/replay tools
SUSPICIOUS_HEADERS = [
    "x-forwarded-host",      # proxy injection (legitimate proxies set x-forwarded-for)
    "x-original-url",        # URL rewrite attacks
    "x-rewrite-url",         # URL rewrite attacks
]

# Paths exempt from rate limiting and UA checks (health, docs, auth)
EXEMPT_PATHS = {"/health", "/openapi.json", "/docs", "/signup", "/login", "/auth/signup", "/auth/login", "/auth/me", "/ws"}


# ── Per-IP Rate Limiter ──────────────────────────────────────────────────

class IPRateLimiter:
    """Sliding-window rate limiter keyed by client IP.

    Thread-safe for single-process async servers (FastAPI/uvicorn).
    For multi-process deployments, replace with Redis-backed limiter.
    """

    def __init__(self, max_requests: int = 60, window_seconds: int = 60):
        self.max_requests = max_requests
        self.window_seconds = window_seconds
        self._hits: dict[str, list[float]] = defaultdict(list)

    def is_allowed(self, ip: str) -> tuple[bool, dict]:
        """Check if a request from this IP is within limits.

        Returns (allowed, info) where info contains rate limit headers.
        """
        now = time.monotonic()
        window_start = now - self.window_seconds

        # Prune expired entries
        hits = self._hits[ip]
        hits[:] = [t for t in hits if t > window_start]

        remaining = max(0, self.max_requests - len(hits))
        info = {
            "limit": self.max_requests,
            "remaining": remaining,
            "window": self.window_seconds,
        }

        if len(hits) >= self.max_requests:
            # Calculate retry-after from oldest entry in window
            retry_after = int(hits[0] - window_start) + 1
            info["retry_after"] = max(1, retry_after)
            return False, info

        hits.append(now)
        info["remaining"] = max(0, self.max_requests - len(hits))
        return True, info

    def cleanup(self) -> int:
        """Remove stale IP entries. Call periodically to prevent memory growth."""
        now = time.monotonic()
        window_start = now - self.window_seconds
        stale = [ip for ip, hits in self._hits.items()
                 if not hits or hits[-1] < window_start]
        for ip in stale:
            del self._hits[ip]
        return len(stale)


# Global rate limiter instance
_ip_rate_limiter = IPRateLimiter(
    max_requests=RATE_LIMIT_MAX_REQUESTS,
    window_seconds=RATE_LIMIT_WINDOW_SECONDS,
)


# ── Security Headers Middleware ──────────────────────────────────────────

SECURITY_HEADERS = {
    "X-Content-Type-Options": "nosniff",
    "X-Frame-Options": "DENY",
    "X-XSS-Protection": "1; mode=block",
    "Referrer-Policy": "strict-origin-when-cross-origin",
    "Cache-Control": "no-store, no-cache, must-revalidate, max-age=0",
    "Pragma": "no-cache",
    "Permissions-Policy": "camera=(), microphone=(), geolocation=()",
}


class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    """Adds security headers to every response and strips server info."""

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        response = await call_next(request)

        # Add security headers
        for header, value in SECURITY_HEADERS.items():
            response.headers[header] = value

        # Strip server version info — prevent fingerprinting
        # MutableHeaders uses del, not pop
        for hdr in ("server", "x-powered-by"):
            if hdr in response.headers:
                del response.headers[hdr]

        return response


# ── Rate Limiting Middleware ─────────────────────────────────────────────

def _get_client_ip(request: Request) -> str:
    """Extract real client IP, respecting X-Forwarded-For from trusted proxies."""
    forwarded = request.headers.get("x-forwarded-for")
    if forwarded:
        # Take the first (client) IP from the chain
        return forwarded.split(",")[0].strip()
    return request.client.host if request.client else "unknown"


class RateLimitMiddleware(BaseHTTPMiddleware):
    """Per-IP rate limiting. Returns 429 when limit exceeded."""

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        # Skip rate limiting for exempt paths
        if request.url.path in EXEMPT_PATHS:
            return await call_next(request)

        # Skip WebSocket upgrades — they are long-lived connections
        if request.headers.get("upgrade", "").lower() == "websocket":
            return await call_next(request)

        client_ip = _get_client_ip(request)
        allowed, info = _ip_rate_limiter.is_allowed(client_ip)

        if not allowed:
            return JSONResponse(
                status_code=429,
                content={"detail": "Rate limit exceeded. Try again later."},
                headers={
                    "Retry-After": str(info.get("retry_after", 60)),
                    "X-RateLimit-Limit": str(info["limit"]),
                    "X-RateLimit-Remaining": "0",
                },
            )

        response = await call_next(request)

        # Add rate limit headers to successful responses
        response.headers["X-RateLimit-Limit"] = str(info["limit"])
        response.headers["X-RateLimit-Remaining"] = str(info["remaining"])

        return response


# ── Request Validation Middleware ────────────────────────────────────────

class RequestValidationMiddleware(BaseHTTPMiddleware):
    """Validates incoming requests for suspicious patterns.

    Blocks:
      - Requests with no User-Agent (bots, curl defaults)
      - Requests with known scraper/dev-tool User-Agents
      - Requests with suspicious proxy/rewrite headers
    """

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        # Skip validation for exempt paths and WebSocket upgrades
        if request.url.path in EXEMPT_PATHS:
            return await call_next(request)
        if request.headers.get("upgrade", "".lower()) == "websocket":
            return await call_next(request)

        # 1. User-Agent validation
        user_agent = request.headers.get("user-agent", "")
        if not user_agent:
            return JSONResponse(
                status_code=403,
                content={"detail": "Forbidden"},
            )

        ua_lower = user_agent.lower()
        for blocked in BLOCKED_USER_AGENTS:
            if blocked in ua_lower:
                return JSONResponse(
                    status_code=403,
                    content={"detail": "Forbidden"},
                )

        # 2. Suspicious header detection
        for header in SUSPICIOUS_HEADERS:
            if request.headers.get(header):
                return JSONResponse(
                    status_code=400,
                    content={"detail": "Bad request"},
                )

        return await call_next(request)


# ── Main Entry Point ─────────────────────────────────────────────────────

def apply_security_hardening(app: FastAPI) -> None:
    """Apply all security hardening to a FastAPI application.

    Call this AFTER creating the FastAPI app but BEFORE defining routes
    (or at least before the app starts serving).

    This function:
      1. Removes any existing CORS middleware and replaces with strict config
      2. Adds rate limiting middleware (60 req/min per IP)
      3. Adds request validation middleware (User-Agent, header checks)
      4. Adds security response headers (X-Content-Type-Options, etc.)
      5. Strips server/version info from responses
      6. Removes version from the root endpoint

    Order matters — middleware executes in REVERSE add order, so:
      - SecurityHeaders runs last (always adds headers)
      - RateLimit runs second (blocks before processing)
      - RequestValidation runs first (blocks before rate counting)
    """

    # Remove existing CORSMiddleware if present, we will re-add with strict config
    # FastAPI stores middleware in app.user_middleware as a list
    app.user_middleware = [
        m for m in app.user_middleware
        if not (hasattr(m, 'cls') and m.cls is CORSMiddleware)
    ]

    # Add strict CORS — only allow known origins, restrict methods/headers
    app.add_middleware(
        CORSMiddleware,
        allow_origins=ALLOWED_ORIGINS,
        allow_credentials=True,
        allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
        allow_headers=[
            "Authorization",
            "Content-Type",
            "X-API-Key",
            "X-Request-ID",
        ],
        expose_headers=[
            "X-RateLimit-Limit",
            "X-RateLimit-Remaining",
            "Retry-After",
        ],
        max_age=600,  # Cache preflight for 10 minutes
    )

    # Add middleware in reverse order (last added = first executed)
    app.add_middleware(SecurityHeadersMiddleware)
    app.add_middleware(RateLimitMiddleware)
    app.add_middleware(RequestValidationMiddleware)

    # Strip version from OpenAPI schema
    if app.openapi_schema:
        app.openapi_schema.get("info", {}).pop("version", None)


def get_rate_limiter() -> IPRateLimiter:
    """Get the global rate limiter instance for status checks or testing."""
    return _ip_rate_limiter
