#!/usr/bin/env python3

"""
Tests for security headers middleware.

These tests validate that security headers are properly added to HTTP responses
based on configuration settings.
"""

from unittest.mock import Mock

import pytest


@pytest.mark.unit
def test_security_headers_enabled_by_default(client):
    """Test that security headers are enabled by default."""
    response = client.get("/")

    # Should get a valid response (200, 302 redirect, or 404)
    assert response.status_code in [200, 302, 404], f"Unexpected status code: {response.status_code}"


@pytest.mark.unit
def test_hsts_header_present(client):
    """Test that HSTS header is present when enabled."""
    from app.config import settings

    # Skip test if HSTS is disabled
    if not settings.security_headers_enabled or not settings.security_header_hsts_enabled:
        pytest.skip("HSTS header is disabled in configuration")

    response = client.get("/")
    assert "Strict-Transport-Security" in response.headers
    assert "max-age" in response.headers["Strict-Transport-Security"]


@pytest.mark.unit
def test_csp_header_present(client):
    """Test that CSP header is present when enabled."""
    from app.config import settings

    # Skip test if CSP is disabled
    if not settings.security_headers_enabled or not settings.security_header_csp_enabled:
        pytest.skip("CSP header is disabled in configuration")

    response = client.get("/")
    assert "Content-Security-Policy" in response.headers
    assert "default-src" in response.headers["Content-Security-Policy"]


@pytest.mark.unit
def test_x_frame_options_header_present(client):
    """Test that X-Frame-Options header is present when enabled."""
    from app.config import settings

    # Skip test if X-Frame-Options is disabled
    if not settings.security_headers_enabled or not settings.security_header_x_frame_options_enabled:
        pytest.skip("X-Frame-Options header is disabled in configuration")

    response = client.get("/")
    assert "X-Frame-Options" in response.headers
    assert response.headers["X-Frame-Options"] in ["DENY", "SAMEORIGIN"]


@pytest.mark.unit
def test_x_content_type_options_header_present(client):
    """Test that X-Content-Type-Options header is present when enabled."""
    from app.config import settings

    # Skip test if X-Content-Type-Options is disabled
    if not settings.security_headers_enabled or not settings.security_header_x_content_type_options_enabled:
        pytest.skip("X-Content-Type-Options header is disabled in configuration")

    response = client.get("/")
    assert "X-Content-Type-Options" in response.headers
    assert response.headers["X-Content-Type-Options"] == "nosniff"


@pytest.mark.unit
def test_security_headers_on_api_endpoints(client):
    """Test that security headers are applied to API endpoints."""
    from app.config import settings

    if not settings.security_headers_enabled:
        pytest.skip("Security headers are disabled in configuration")

    response = client.get("/api/diagnostic/health")

    # Check that at least some security headers are present
    security_headers = [
        "Strict-Transport-Security",
        "Content-Security-Policy",
        "X-Frame-Options",
        "X-Content-Type-Options",
    ]
    present_headers = [h for h in security_headers if h in response.headers]
    assert len(present_headers) > 0, "No security headers found on API endpoint"


@pytest.mark.security
def test_hsts_header_value_format(client):
    """Test that HSTS header has correct format."""
    from app.config import settings

    if not settings.security_headers_enabled or not settings.security_header_hsts_enabled:
        pytest.skip("HSTS header is disabled in configuration")

    response = client.get("/")
    if "Strict-Transport-Security" in response.headers:
        hsts_value = response.headers["Strict-Transport-Security"]
        assert "max-age=" in hsts_value, "HSTS header missing max-age directive"
        # Extract max-age value
        parts = hsts_value.split(";")
        max_age_part = [p.strip() for p in parts if p.strip().startswith("max-age=")]
        assert len(max_age_part) > 0, "HSTS header missing max-age value"


@pytest.mark.security
def test_csp_header_value_format(client):
    """Test that CSP header has correct format."""
    from app.config import settings

    if not settings.security_headers_enabled or not settings.security_header_csp_enabled:
        pytest.skip("CSP header is disabled in configuration")

    response = client.get("/")
    if "Content-Security-Policy" in response.headers:
        csp_value = response.headers["Content-Security-Policy"]
        # CSP should have at least a default-src directive
        assert "default-src" in csp_value or "script-src" in csp_value, "CSP header missing required directives"


@pytest.mark.security
def test_x_frame_options_valid_value(client):
    """Test that X-Frame-Options header has valid value."""
    from app.config import settings

    if not settings.security_headers_enabled or not settings.security_header_x_frame_options_enabled:
        pytest.skip("X-Frame-Options header is disabled in configuration")

    response = client.get("/")
    if "X-Frame-Options" in response.headers:
        x_frame_value = response.headers["X-Frame-Options"]
        valid_values = ["DENY", "SAMEORIGIN"]
        # Note: ALLOW-FROM is deprecated in modern browsers; use CSP frame-ancestors instead
        assert x_frame_value in valid_values or x_frame_value.startswith("ALLOW-FROM"), (
            f"Invalid X-Frame-Options value: {x_frame_value}"
        )


@pytest.mark.integration
def test_security_headers_configuration_loading():
    """Test that security header configuration is loaded correctly."""
    from app.config import settings

    # Verify that security header configuration attributes exist
    assert hasattr(settings, "security_headers_enabled")
    assert hasattr(settings, "security_header_hsts_enabled")
    assert hasattr(settings, "security_header_hsts_value")
    assert hasattr(settings, "security_header_csp_enabled")
    assert hasattr(settings, "security_header_csp_value")
    assert hasattr(settings, "security_header_x_frame_options_enabled")
    assert hasattr(settings, "security_header_x_frame_options_value")
    assert hasattr(settings, "security_header_x_content_type_options_enabled")

    # Verify that boolean settings are actual booleans
    assert isinstance(settings.security_headers_enabled, bool)
    assert isinstance(settings.security_header_hsts_enabled, bool)
    assert isinstance(settings.security_header_csp_enabled, bool)
    assert isinstance(settings.security_header_x_frame_options_enabled, bool)
    assert isinstance(settings.security_header_x_content_type_options_enabled, bool)

    # Verify that string settings are actual strings
    assert isinstance(settings.security_header_hsts_value, str)
    assert isinstance(settings.security_header_csp_value, str)
    assert isinstance(settings.security_header_x_frame_options_value, str)


@pytest.mark.integration
def test_middleware_respects_configuration():
    """Test that middleware respects individual header enable/disable settings."""
    from app.config import settings
    from app.middleware.security_headers import SecurityHeadersMiddleware

    # Create middleware instance
    middleware = SecurityHeadersMiddleware(app=None, config=settings)

    # Verify that middleware stores configuration
    assert middleware.config == settings
    assert middleware.enabled == settings.security_headers_enabled


@pytest.mark.unit
class TestSecurityHeadersMiddleware:
    """Tests for SecurityHeadersMiddleware class."""

    def test_middleware_initialization(self):
        """Test middleware initializes with configuration."""
        from app.config import settings
        from app.middleware.security_headers import SecurityHeadersMiddleware

        middleware = SecurityHeadersMiddleware(app=None, config=settings)
        assert middleware.config == settings
        assert middleware.enabled == settings.security_headers_enabled

    @pytest.mark.asyncio
    async def test_dispatch_adds_headers_when_enabled(self):
        """Test dispatch adds security headers when enabled."""
        from fastapi import Response

        from app.config import settings
        from app.middleware.security_headers import SecurityHeadersMiddleware

        if not settings.security_headers_enabled:
            pytest.skip("Security headers disabled in configuration")

        middleware = SecurityHeadersMiddleware(app=None, config=settings)

        # Mock request and call_next
        mock_request = Mock()
        mock_response = Response(content="test", status_code=200)

        async def mock_call_next(request):
            return mock_response

        result = await middleware.dispatch(mock_request, mock_call_next)

        # At least some headers should be present
        assert isinstance(result, Response)

    @pytest.mark.asyncio
    async def test_dispatch_skips_headers_when_disabled(self):
        """Test dispatch skips headers when disabled."""
        from fastapi import Response

        from app.middleware.security_headers import SecurityHeadersMiddleware

        # Create a config copy with headers disabled
        mock_config = Mock()
        mock_config.security_headers_enabled = False

        middleware = SecurityHeadersMiddleware(app=None, config=mock_config)

        mock_request = Mock()
        mock_response = Response(content="test", status_code=200)

        async def mock_call_next(request):
            return mock_response

        result = await middleware.dispatch(mock_request, mock_call_next)

        # Headers should not be added
        assert isinstance(result, Response)

    def test_add_security_headers_hsts(self):
        """Test _add_security_headers adds HSTS header."""
        from fastapi import Response

        from app.middleware.security_headers import SecurityHeadersMiddleware

        mock_config = Mock()
        mock_config.security_headers_enabled = True
        mock_config.security_header_hsts_enabled = True
        mock_config.security_header_hsts_value = "max-age=31536000; includeSubDomains"
        mock_config.security_header_csp_enabled = False
        mock_config.security_header_x_frame_options_enabled = False
        mock_config.security_header_x_content_type_options_enabled = False

        middleware = SecurityHeadersMiddleware(app=None, config=mock_config)

        response = Response(content="test")
        middleware._add_security_headers(response)

        assert "Strict-Transport-Security" in response.headers
        assert "max-age" in response.headers["Strict-Transport-Security"]

    def test_add_security_headers_csp(self):
        """Test _add_security_headers adds CSP header."""
        from fastapi import Response

        from app.middleware.security_headers import SecurityHeadersMiddleware

        mock_config = Mock()
        mock_config.security_headers_enabled = True
        mock_config.security_header_hsts_enabled = False
        mock_config.security_header_csp_enabled = True
        mock_config.security_header_csp_value = "default-src 'self'; script-src 'self' 'unsafe-inline'"
        mock_config.security_header_x_frame_options_enabled = False
        mock_config.security_header_x_content_type_options_enabled = False

        middleware = SecurityHeadersMiddleware(app=None, config=mock_config)

        response = Response(content="test")
        middleware._add_security_headers(response)

        assert "Content-Security-Policy" in response.headers
        assert "default-src" in response.headers["Content-Security-Policy"]

    def test_add_security_headers_x_frame_options(self):
        """Test _add_security_headers adds X-Frame-Options header."""
        from fastapi import Response

        from app.middleware.security_headers import SecurityHeadersMiddleware

        mock_config = Mock()
        mock_config.security_headers_enabled = True
        mock_config.security_header_hsts_enabled = False
        mock_config.security_header_csp_enabled = False
        mock_config.security_header_x_frame_options_enabled = True
        mock_config.security_header_x_frame_options_value = "DENY"
        mock_config.security_header_x_content_type_options_enabled = False

        middleware = SecurityHeadersMiddleware(app=None, config=mock_config)

        response = Response(content="test")
        middleware._add_security_headers(response)

        assert "X-Frame-Options" in response.headers
        assert response.headers["X-Frame-Options"] == "DENY"

    def test_add_security_headers_x_content_type_options(self):
        """Test _add_security_headers adds X-Content-Type-Options header."""
        from fastapi import Response

        from app.middleware.security_headers import SecurityHeadersMiddleware

        mock_config = Mock()
        mock_config.security_headers_enabled = True
        mock_config.security_header_hsts_enabled = False
        mock_config.security_header_csp_enabled = False
        mock_config.security_header_x_frame_options_enabled = False
        mock_config.security_header_x_content_type_options_enabled = True

        middleware = SecurityHeadersMiddleware(app=None, config=mock_config)

        response = Response(content="test")
        middleware._add_security_headers(response)

        assert "X-Content-Type-Options" in response.headers
        assert response.headers["X-Content-Type-Options"] == "nosniff"

    def test_add_all_security_headers(self):
        """Test _add_security_headers adds all headers when all enabled."""
        from fastapi import Response

        from app.middleware.security_headers import SecurityHeadersMiddleware

        mock_config = Mock()
        mock_config.security_headers_enabled = True
        mock_config.security_header_hsts_enabled = True
        mock_config.security_header_hsts_value = "max-age=31536000"
        mock_config.security_header_csp_enabled = True
        mock_config.security_header_csp_value = "default-src 'self'"
        mock_config.security_header_x_frame_options_enabled = True
        mock_config.security_header_x_frame_options_value = "SAMEORIGIN"
        mock_config.security_header_x_content_type_options_enabled = True

        middleware = SecurityHeadersMiddleware(app=None, config=mock_config)

        response = Response(content="test")
        middleware._add_security_headers(response)

        assert "Strict-Transport-Security" in response.headers
        assert "Content-Security-Policy" in response.headers
        assert "X-Frame-Options" in response.headers
        assert "X-Content-Type-Options" in response.headers
