"""
Django System Checks for Security Configuration.

This module implements Django system checks that validate security settings
at startup. These checks help catch security misconfigurations before
deployment.

Usage:
    These checks run automatically when Django starts. They can also be run
    manually with: python manage.py check --tag=security

Security checks included:
- SECRET_KEY validation (not default, sufficient entropy)
- DEBUG mode check (should be False in production)
- ALLOWED_HOSTS check (should be configured in production)
- Security headers validation
- HTTPS settings validation
- Cookie security settings
"""

import os
import re

from django.conf import settings
from django.core.checks import Error, Tags, Warning, register

# =============================================================================
# Secret Key Validation
# =============================================================================


@register(Tags.security)
def check_secret_key(app_configs, **kwargs):
    """
    Check that SECRET_KEY is properly configured.

    Validates:
    - Key is not a known default/placeholder value
    - Key has sufficient entropy (length and character variety)
    """
    errors = []
    secret_key = getattr(settings, "SECRET_KEY", "")

    # Check for empty or missing key
    if not secret_key:
        errors.append(
            Error(
                "SECRET_KEY is not set.",
                hint="Set a strong, random SECRET_KEY in your environment.",
                id="security.E001",
            )
        )
        return errors

    # Check for known insecure default values
    insecure_defaults = [
        "django-insecure",
        "your-secret-key",
        "change-me",
        "changeme",
        "secret",
        "xxx",
        "test",
        "development",
        "dev-key",
    ]

    key_lower = secret_key.lower()
    for default in insecure_defaults:
        if default in key_lower:
            errors.append(
                Error(
                    f'SECRET_KEY appears to contain an insecure default value: "{default}"',
                    hint='Generate a new secret key using: python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"',
                    id="security.E002",
                )
            )
            break

    # Check minimum length (Django recommends at least 50 characters)
    if len(secret_key) < 50:
        errors.append(
            Warning(
                f"SECRET_KEY is only {len(secret_key)} characters long.",
                hint="A secret key should be at least 50 characters for proper security.",
                id="security.W001",
            )
        )

    # Check for sufficient character variety
    has_upper = bool(re.search(r"[A-Z]", secret_key))
    has_lower = bool(re.search(r"[a-z]", secret_key))
    has_digit = bool(re.search(r"[0-9]", secret_key))
    has_special = bool(re.search(r'[!@#$%^&*()_+\-=\[\]{};\':"\\|,.<>\/?]', secret_key))

    char_types = sum([has_upper, has_lower, has_digit, has_special])
    if char_types < 3:
        errors.append(
            Warning(
                "SECRET_KEY lacks character variety.",
                hint="A good secret key should contain uppercase, lowercase, digits, and special characters.",
                id="security.W002",
            )
        )

    return errors


# =============================================================================
# Debug Mode Check
# =============================================================================


@register(Tags.security)
def check_debug_mode(app_configs, **kwargs):
    """
    Check that DEBUG is False in production-like environments.
    """
    errors = []

    # Check if we're in a production-like environment
    env = os.environ.get("DJANGO_SETTINGS_MODULE", "")
    is_production = "production" in env.lower() or "prod" in env.lower()

    if is_production and settings.DEBUG:
        errors.append(
            Error(
                "DEBUG is True in what appears to be a production environment.",
                hint="Set DEBUG=False in production settings.",
                id="security.E003",
            )
        )

    # Also check if DEBUG is True with ALLOWED_HOSTS configured
    # (indicates possible production deployment with debug on)
    if settings.DEBUG and settings.ALLOWED_HOSTS and "*" not in settings.ALLOWED_HOSTS:  # noqa: SIM102
        if len(settings.ALLOWED_HOSTS) > 0 and "localhost" not in settings.ALLOWED_HOSTS[0]:
            errors.append(
                Warning(
                    "DEBUG is True but ALLOWED_HOSTS contains non-localhost values.",
                    hint="This may indicate DEBUG is accidentally enabled in a deployed environment.",
                    id="security.W003",
                )
            )

    return errors


# =============================================================================
# ALLOWED_HOSTS Check
# =============================================================================


@register(Tags.security)
def check_allowed_hosts(app_configs, **kwargs):
    """
    Check ALLOWED_HOSTS configuration.
    """
    errors = []
    allowed_hosts = getattr(settings, "ALLOWED_HOSTS", [])

    if not settings.DEBUG:
        # In non-debug mode, ALLOWED_HOSTS must be set
        if not allowed_hosts:
            errors.append(
                Error(
                    "ALLOWED_HOSTS is empty but DEBUG is False.",
                    hint="Set ALLOWED_HOSTS to a list of allowed hostnames.",
                    id="security.E004",
                )
            )
        elif "*" in allowed_hosts:
            errors.append(
                Error(
                    'ALLOWED_HOSTS contains "*" which allows all hosts.',
                    hint="Specify explicit hostnames instead of wildcards.",
                    id="security.E005",
                )
            )

    return errors


# =============================================================================
# Security Headers Check
# =============================================================================


@register(Tags.security)
def check_security_headers(app_configs, **kwargs):
    """
    Check that security headers are properly configured.
    """
    errors = []

    # Check X-Frame-Options
    x_frame_options = getattr(settings, "X_FRAME_OPTIONS", None)
    if x_frame_options not in ("DENY", "SAMEORIGIN"):
        errors.append(
            Warning(
                f'X_FRAME_OPTIONS is set to "{x_frame_options}" or not set.',
                hint='Set X_FRAME_OPTIONS to "DENY" or "SAMEORIGIN" to prevent clickjacking.',
                id="security.W004",
            )
        )

    # Check content type sniffing protection
    if not getattr(settings, "SECURE_CONTENT_TYPE_NOSNIFF", False):
        errors.append(
            Warning(
                "SECURE_CONTENT_TYPE_NOSNIFF is not enabled.",
                hint="Set SECURE_CONTENT_TYPE_NOSNIFF = True to prevent MIME type sniffing.",
                id="security.W005",
            )
        )

    # Check referrer policy
    referrer_policy = getattr(settings, "SECURE_REFERRER_POLICY", None)
    if not referrer_policy:
        errors.append(
            Warning(
                "SECURE_REFERRER_POLICY is not set.",
                hint="Set SECURE_REFERRER_POLICY to control referrer header behavior.",
                id="security.W006",
            )
        )

    return errors


# =============================================================================
# HTTPS Settings Check
# =============================================================================


@register(Tags.security)
def check_https_settings(app_configs, **kwargs):
    """
    Check HTTPS-related security settings for production.
    """
    errors = []

    # Skip these checks in debug mode
    if settings.DEBUG:
        return errors

    # Check SSL redirect
    if not getattr(settings, "SECURE_SSL_REDIRECT", False):
        errors.append(
            Warning(
                "SECURE_SSL_REDIRECT is not enabled.",
                hint="Set SECURE_SSL_REDIRECT = True to redirect HTTP to HTTPS.",
                id="security.W007",
            )
        )

    # Check HSTS settings
    hsts_seconds = getattr(settings, "SECURE_HSTS_SECONDS", 0)
    if hsts_seconds < 31536000:  # Less than 1 year
        errors.append(
            Warning(
                f"SECURE_HSTS_SECONDS is {hsts_seconds} (less than 1 year).",
                hint="Set SECURE_HSTS_SECONDS to at least 31536000 (1 year) for HSTS preload eligibility.",
                id="security.W008",
            )
        )

    if not getattr(settings, "SECURE_HSTS_INCLUDE_SUBDOMAINS", False):
        errors.append(
            Warning(
                "SECURE_HSTS_INCLUDE_SUBDOMAINS is not enabled.",
                hint="Set SECURE_HSTS_INCLUDE_SUBDOMAINS = True to include all subdomains in HSTS.",
                id="security.W009",
            )
        )

    return errors


# =============================================================================
# Cookie Security Check
# =============================================================================


@register(Tags.security)
def check_cookie_security(app_configs, **kwargs):
    """
    Check cookie security settings for production.
    """
    errors = []

    # Skip in debug mode
    if settings.DEBUG:
        return errors

    # Check session cookie security
    if not getattr(settings, "SESSION_COOKIE_SECURE", False):
        errors.append(
            Warning(
                "SESSION_COOKIE_SECURE is not enabled.",
                hint="Set SESSION_COOKIE_SECURE = True to only send session cookies over HTTPS.",
                id="security.W010",
            )
        )

    if not getattr(settings, "SESSION_COOKIE_HTTPONLY", True):
        errors.append(
            Warning(
                "SESSION_COOKIE_HTTPONLY is disabled.",
                hint="Set SESSION_COOKIE_HTTPONLY = True to prevent JavaScript access to session cookies.",
                id="security.W011",
            )
        )

    # Check CSRF cookie security
    if not getattr(settings, "CSRF_COOKIE_SECURE", False):
        errors.append(
            Warning(
                "CSRF_COOKIE_SECURE is not enabled.",
                hint="Set CSRF_COOKIE_SECURE = True to only send CSRF cookies over HTTPS.",
                id="security.W012",
            )
        )

    # Check SameSite attributes
    session_samesite = getattr(settings, "SESSION_COOKIE_SAMESITE", "Lax")
    if session_samesite not in ("Strict", "Lax"):
        errors.append(
            Warning(
                f'SESSION_COOKIE_SAMESITE is set to "{session_samesite}".',
                hint='Set SESSION_COOKIE_SAMESITE to "Strict" or "Lax" for CSRF protection.',
                id="security.W013",
            )
        )

    return errors


# =============================================================================
# Database Security Check
# =============================================================================


@register(Tags.security)
def check_database_security(app_configs, **kwargs):
    """
    Check database connection security settings.
    """
    errors = []

    # Skip in debug mode
    if settings.DEBUG:
        return errors

    databases = getattr(settings, "DATABASES", {})
    default_db = databases.get("default", {})

    # Check for empty password
    if not default_db.get("PASSWORD") and default_db.get("ENGINE", "").endswith("postgresql"):
        errors.append(
            Warning(
                "Database password is empty.",
                hint="Set a strong password for database authentication.",
                id="security.W014",
            )
        )

    # Check for SSL mode in PostgreSQL
    options = default_db.get("OPTIONS", {})
    if "sslmode" not in str(options) and default_db.get("ENGINE", "").endswith("postgresql"):
        errors.append(
            Warning(
                "Database SSL mode is not explicitly configured.",
                hint="Consider setting sslmode in database OPTIONS for encrypted connections.",
                id="security.W015",
            )
        )

    return errors
