"""
Security Headers Middleware

Adds security headers to all HTTP responses to protect against common attacks.

Headers added:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- X-XSS-Protection: 1; mode=block
- Strict-Transport-Security (HSTS)
- Content-Security-Policy
- Referrer-Policy
- Permissions-Policy

Usage:
    from middleware import SecurityHeadersMiddleware

    app.add_middleware(SecurityHeadersMiddleware)

    # Or with custom configuration:
    app.add_middleware(
        SecurityHeadersMiddleware,
        hsts_enabled=True,
        csp_policy="default-src 'self'",
    )
"""

import os
from dataclasses import dataclass, field
from typing import Dict, List, Optional

from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
from starlette.responses import Response


@dataclass
class SecurityHeadersConfig:
    """Configuration for security headers."""

    # X-Content-Type-Options
    content_type_options: str = "nosniff"

    # X-Frame-Options
    frame_options: str = "DENY"

    # X-XSS-Protection (legacy, but still useful for older browsers)
    xss_protection: str = "1; mode=block"

    # Strict-Transport-Security
    hsts_enabled: bool = True
    hsts_max_age: int = 31536000  # 1 year
    hsts_include_subdomains: bool = True
    hsts_preload: bool = False

    # Content-Security-Policy
    csp_enabled: bool = True
    csp_policy: str = "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https:; frame-ancestors 'none'"

    # Referrer-Policy
    referrer_policy: str = "strict-origin-when-cross-origin"

    # Permissions-Policy (formerly Feature-Policy)
    permissions_policy: str = "geolocation=(), microphone=(), camera=()"

    # Cross-Origin headers
    cross_origin_opener_policy: str = "same-origin"
    cross_origin_embedder_policy: str = "require-corp"
    cross_origin_resource_policy: str = "same-origin"

    # Development mode (relaxes some restrictions)
    development_mode: bool = False

    # Excluded paths (e.g., for health checks)
    excluded_paths: List[str] = field(default_factory=lambda: ["/health", "/metrics"])


class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    """
    Middleware that adds security headers to all responses.

    Attributes:
        config: SecurityHeadersConfig instance
    """

    def __init__(
        self,
        app,
        config: Optional[SecurityHeadersConfig] = None,
        # Individual overrides for convenience
        hsts_enabled: Optional[bool] = None,
        csp_policy: Optional[str] = None,
        csp_enabled: Optional[bool] = None,
        development_mode: Optional[bool] = None,
    ):
        super().__init__(app)

        # Use provided config or create default
        self.config = config or SecurityHeadersConfig()

        # Apply individual overrides
        if hsts_enabled is not None:
            self.config.hsts_enabled = hsts_enabled
        if csp_policy is not None:
            self.config.csp_policy = csp_policy
        if csp_enabled is not None:
            self.config.csp_enabled = csp_enabled
        if development_mode is not None:
            self.config.development_mode = development_mode

        # Auto-detect development mode from environment
        if development_mode is None:
            env = os.getenv("ENVIRONMENT", "development")
            self.config.development_mode = env.lower() in ("development", "dev", "local")

    def _build_hsts_header(self) -> str:
        """Build the Strict-Transport-Security header value."""
        parts = [f"max-age={self.config.hsts_max_age}"]
        if self.config.hsts_include_subdomains:
            parts.append("includeSubDomains")
        if self.config.hsts_preload:
            parts.append("preload")
        return "; ".join(parts)

    def _get_headers(self) -> Dict[str, str]:
        """Build the security headers dictionary."""
        headers = {}

        # Always add these headers
        headers["X-Content-Type-Options"] = self.config.content_type_options
        headers["X-Frame-Options"] = self.config.frame_options
        headers["X-XSS-Protection"] = self.config.xss_protection
        headers["Referrer-Policy"] = self.config.referrer_policy

        # HSTS (only in production or if explicitly enabled)
        if self.config.hsts_enabled and not self.config.development_mode:
            headers["Strict-Transport-Security"] = self._build_hsts_header()

        # Content-Security-Policy
        if self.config.csp_enabled:
            headers["Content-Security-Policy"] = self.config.csp_policy

        # Permissions-Policy
        if self.config.permissions_policy:
            headers["Permissions-Policy"] = self.config.permissions_policy

        # Cross-Origin headers (relaxed in development)
        if not self.config.development_mode:
            headers["Cross-Origin-Opener-Policy"] = self.config.cross_origin_opener_policy
            # Note: COEP can break loading of external resources, be careful
            # headers["Cross-Origin-Embedder-Policy"] = self.config.cross_origin_embedder_policy
            headers["Cross-Origin-Resource-Policy"] = self.config.cross_origin_resource_policy

        return headers

    async def dispatch(self, request: Request, call_next) -> Response:
        # Skip security headers for excluded paths
        if request.url.path in self.config.excluded_paths:
            return await call_next(request)

        # Process request
        response = await call_next(request)

        # Add security headers
        for header_name, header_value in self._get_headers().items():
            response.headers[header_name] = header_value

        return response


def get_default_csp_for_environment(environment: str) -> str:
    """
    Get a sensible default CSP for the given environment.

    Args:
        environment: "development", "staging", or "production"

    Returns:
        CSP policy string
    """
    if environment.lower() in ("development", "dev", "local"):
        # Relaxed CSP for development
        return (
            "default-src 'self' localhost:* ws://localhost:*; "
            "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data: https: blob:; "
            "font-src 'self' data:; "
            "connect-src 'self' localhost:* ws://localhost:* https:; "
            "frame-ancestors 'self'"
        )
    else:
        # Strict CSP for production
        return (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-inline'; "
            "style-src 'self' 'unsafe-inline'; "
            "img-src 'self' data: https:; "
            "font-src 'self' data:; "
            "connect-src 'self' https://breakpilot.app https://*.breakpilot.app; "
            "frame-ancestors 'none'"
        )
