"""
Django settings for config project.

Generated by 'django-admin startproject' using Django 5.2.7.

For more information on this file, see
https://docs.djangoproject.com/en/5.2/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.2/ref/settings/
"""

from pathlib import Path
import os
from dotenv import load_dotenv

# 加载环境变量
load_dotenv()


def get_bool_env(key: str, default: bool = False) -> bool:
    """获取布尔值环境变量，兼容多种写法（true/True/TRUE/1/yes）"""
    value = os.getenv(key, str(default)).lower()
    return value in ('true', '1', 'yes', 'on')

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv('SECRET_KEY', 'django-insecure-default-key')

# SECURITY WARNING: don't run with debug turned on in production!
# 安全优先：默认为 False，开发环境需显式设置 DEBUG=True
DEBUG = get_bool_env('DEBUG', False)

ALLOWED_HOSTS = ['*']


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    # 第三方应用
    'rest_framework',
    'drf_yasg',
    'corsheaders',
    'channels',  # WebSocket 支持
    # 业务应用
    'apps.common',   # 通用工具
    'apps.targets',  # 扫描目标管理
    'apps.scan',     # 扫描任务管理
    'apps.engine',   # 扫描引擎管理
    'apps.asset',    # 资产管理
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'corsheaders.middleware.CorsMiddleware',  # CORS 中间件（必须在 CommonMiddleware 之前）
    'django.middleware.common.CommonMiddleware',
    # 'django.middleware.csrf.CsrfViewMiddleware',  # 已禁用 CSRF 校验
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],  # 添加自定义模板目录
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

WSGI_APPLICATION = 'config.wsgi.application'

# ASGI 应用配置（用于 WebSocket）
ASGI_APPLICATION = 'config.asgi.application'


# Database - PostgreSQL 配置
# https://docs.djangoproject.com/en/5.2/ref/settings/#databases

DATABASES = {
    'default': {
        'ENGINE': os.getenv('DB_ENGINE', 'django.db.backends.postgresql'),
        'NAME': os.getenv('DB_NAME', 'xingrin'),
        'USER': os.getenv('DB_USER', 'postgres'),
        'PASSWORD': os.getenv('DB_PASSWORD', ''),
        'HOST': os.getenv('DB_HOST', 'localhost'),
        'PORT': os.getenv('DB_PORT', '5432'),
        
        # 连接池配置：针对长时间扫描任务优化
        # 说明：
        # - 0: 每次请求后关闭（适合不稳定的远程连接）
        # - 60-120: 推荐值（平衡性能和资源占用）
        # - 300+: 适合长时间任务（减少连接重建开销）
        # - None: 永久连接（仅适合专用连接池，不推荐）
        'CONN_MAX_AGE': int(os.getenv('DB_CONN_MAX_AGE', '60')),  # 降低到 60 秒，更快检测失效连接
        
        # Django 4.1+ 连接健康检查：每次使用前验证连接是否有效
        # 解决 "server closed the connection unexpectedly" 问题
        'CONN_HEALTH_CHECKS': True,
        
        # PostgreSQL 特定选项 - 针对远程数据库优化
        'OPTIONS': {
            'connect_timeout': 30,  # 连接超时 30 秒（远程数据库需要更长时间）
            'options': '-c statement_timeout=60000 -c idle_in_transaction_session_timeout=300000',  # SQL 语句超时 60 秒，事务空闲超时 5 分钟
            'keepalives': 1,  # 启用 TCP keepalive
            'keepalives_idle': 30,  # TCP keepalive 空闲时间 30 秒（更积极地检测断连）
            'keepalives_interval': 10,  # TCP keepalive 间隔 10 秒
            'keepalives_count': 5,  # TCP keepalive 重试次数
            # 性能优化参数
            'sslmode': 'disable',  # 禁用SSL以减少连接延迟（如果网络安全可控）
            'application_name': 'xingrin_scanner',  # 标识应用名称，便于监控
        }
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.2/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]


# Internationalization
# https://docs.djangoproject.com/en/5.2/topics/i18n/

LANGUAGE_CODE = os.getenv('LANGUAGE_CODE', 'zh-hans')

TIME_ZONE = os.getenv('TIME_ZONE', 'Asia/Shanghai')

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.2/howto/static-files/

STATIC_URL = 'static/'

# Default primary key field type
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# ==================== Worker API Key 配置 ====================
# Worker 节点认证密钥（从环境变量读取）
WORKER_API_KEY = os.environ.get('WORKER_API_KEY', '')

# ==================== REST Framework 配置 ====================
REST_FRAMEWORK = {
    'DEFAULT_PAGINATION_CLASS': 'apps.common.pagination.BasePagination',  # 使用基础分页器
    
    # Session 认证（禁用 CSRF，前后端分离项目通过 CORS 控制跨域）
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'apps.common.authentication.CsrfExemptSessionAuthentication',
    ],
    
    # 全局权限配置：默认需要认证，公开端点和 Worker 端点在权限类中单独处理
    'DEFAULT_PERMISSION_CLASSES': [
        'apps.common.permissions.IsAuthenticatedOrPublic',
    ],
    
    # 自定义异常处理器：统一 401/403 错误响应格式
    'EXCEPTION_HANDLER': 'apps.common.exception_handlers.custom_exception_handler',
    
    # JSON 命名格式转换：后端 snake_case ↔ 前端 camelCase
    'DEFAULT_RENDERER_CLASSES': (
        'djangorestframework_camel_case.render.CamelCaseJSONRenderer',  # 响应数据转换为 camelCase
        'djangorestframework_camel_case.render.CamelCaseBrowsableAPIRenderer',  # 浏览器 API 也使用 camelCase
    ),
    'DEFAULT_PARSER_CLASSES': (
        'djangorestframework_camel_case.parser.CamelCaseJSONParser',  # 请求数据从 camelCase 转换为 snake_case
        'djangorestframework_camel_case.parser.CamelCaseFormParser',  # 表单数据也支持转换
        'djangorestframework_camel_case.parser.CamelCaseMultiPartParser',  # 文件上传支持转换
    ),
    
    # 转换配置
    'JSON_UNDERSCOREIZE': {
        'no_underscore_before_number': True,  # 数字前不加下划线 (field1 不会变成 field_1)
    },
}

# ==================== CORS 配置 ====================
# 允许所有来源（前后端分离项目，安全性由认证系统保障）
CORS_ALLOW_ALL_ORIGINS = os.getenv('CORS_ALLOW_ALL_ORIGINS', 'True').lower() == 'true'
CORS_ALLOW_CREDENTIALS = True

# ==================== CSRF 配置 ====================
CSRF_TRUSTED_ORIGINS = os.getenv('CSRF_TRUSTED_ORIGINS', 'http://localhost:3000,http://127.0.0.1:3000').split(',')

# ==================== Session 配置 ====================
SESSION_COOKIE_AGE = 60 * 60 * 24 * 7  # 7 天
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SAMESITE = 'Lax'  # 跨站请求时发送 cookie

# ==================== 扫描结果存储和清理配置 ====================

# 扫描结果存储目录（智能路径配置）
_scan_results_dir_env = os.getenv('SCAN_RESULTS_DIR')
if _scan_results_dir_env:
    # 使用环境变量指定的路径（支持相对和绝对路径）
    if os.path.isabs(_scan_results_dir_env):
        SCAN_RESULTS_DIR = _scan_results_dir_env  # 绝对路径
    else:
        SCAN_RESULTS_DIR = str(BASE_DIR / _scan_results_dir_env)  # 相对路径转绝对路径
else:
    # 默认使用项目目录下的 results 文件夹
    SCAN_RESULTS_DIR = str(BASE_DIR / 'results')

# 扫描结果保留时间（单位：天）
SCAN_RESULTS_RETENTION_DAYS = int(os.getenv('SCAN_RETENTION_DAYS', '3'))


# ==================== Redis 配置 ====================
# Redis 配置（用于 WebSocket Channel Layer）
REDIS_HOST = os.getenv('REDIS_HOST', 'localhost')
REDIS_PORT = int(os.getenv('REDIS_PORT', 6379))
REDIS_DB = int(os.getenv('REDIS_DB', 0))

# Channels Layer 配置（WebSocket 后端）
CHANNEL_LAYERS = {
    'default': {
        'BACKEND': 'channels_redis.core.RedisChannelLayer',
        'CONFIG': {
            'hosts': [(REDIS_HOST, REDIS_PORT)],
            'capacity': 1500,  # 单个通道最大消息数
            'expiry': 10,  # 消息过期时间（秒）
        },
    },
}

# ==================== 日志配置 ====================
# 日志配置说明：
# 1. 开发环境（DEBUG=True）：
#    - 默认 DEBUG 级别，输出详细调试信息
#    - 控制台彩色输出，便于调试
#    - 可选文件输出（配置 LOG_DIR）
#
# 2. 生产环境（DEBUG=False）：
#    - 默认 INFO 级别，只输出关键信息
#    - 同时输出到控制台和文件（配置 LOG_DIR）
#    - 文件自动轮转，避免日志文件过大
#
# 3. 环境变量控制：
#    - LOG_LEVEL: 全局日志级别（DEBUG/INFO/WARNING/ERROR/CRITICAL）
#    - LOG_DIR: 日志文件目录（留空则不输出文件）
#
from config.logging_config import get_logging_config

LOGGING = get_logging_config(debug=DEBUG)

# 命令执行日志开关（供 apps.scan.utils.command_executor 使用）
ENABLE_COMMAND_LOGGING = get_bool_env('ENABLE_COMMAND_LOGGING', True)

# ==================== 数据目录配置（统一使用 /opt/xingrin） ====================
# 所有数据目录统一挂载到 /opt/xingrin，便于管理和备份

# 扫描工具基础路径（worker 容器内，符合 FHS 标准）
# 使用 /opt/xingrin-tools/bin 隔离项目专用扫描工具，避免与系统工具或 Python 包冲突
SCAN_TOOLS_BASE_PATH = os.getenv('SCAN_TOOLS_PATH', '/opt/xingrin-tools/bin')

# 字典文件基础路径
WORDLISTS_BASE_PATH = os.getenv('WORDLISTS_PATH', '/opt/xingrin/wordlists')

# 指纹库基础路径
FINGERPRINTS_BASE_PATH = os.getenv('FINGERPRINTS_PATH', '/opt/xingrin/fingerprints')

# Nuclei 模板仓库根目录（存放 git clone 的仓库）
NUCLEI_TEMPLATES_REPOS_BASE_DIR = os.getenv('NUCLEI_TEMPLATES_REPOS_DIR', '/opt/xingrin/nuclei-repos')

# Nuclei 模板基础路径（custom / public 两类模板目录，已废弃，保留兼容）
NUCLEI_CUSTOM_TEMPLATES_DIR = os.getenv('NUCLEI_CUSTOM_TEMPLATES_DIR', '/opt/xingrin/nuclei-templates/custom')
NUCLEI_PUBLIC_TEMPLATES_DIR = os.getenv('NUCLEI_PUBLIC_TEMPLATES_DIR', '/opt/xingrin/nuclei-templates/public')

# Nuclei 官方模板仓库地址
NUCLEI_TEMPLATES_REPO_URL = os.getenv('NUCLEI_TEMPLATES_REPO_URL', 'https://github.com/projectdiscovery/nuclei-templates.git')

# 对外访问主机与端口（供 Worker 访问 Django 使用）
PUBLIC_HOST = os.getenv('PUBLIC_HOST', 'localhost').strip()
PUBLIC_PORT = os.getenv('PUBLIC_PORT', '8083').strip()  # 对外 HTTPS 端口
SERVER_PORT = os.getenv('SERVER_PORT', '8888')

# ============================================
# 任务分发器配置（负载感知调度）
# ============================================
# Docker 镜像版本配置（统一管理，确保所有组件版本一致）
DOCKER_USER = os.getenv('DOCKER_USER', 'yyhuni')
IMAGE_TAG = os.getenv('IMAGE_TAG', '')

# 任务执行器镜像配置（task_distributor 用于创建 worker 容器执行扫描任务）
# 
# 版本一致性说明：
# - 生产环境：IMAGE_TAG 锁定版本，确保 server 和 worker 版本一致
# - 开发环境：可通过 TASK_EXECUTOR_IMAGE 环境变量指向本地构建镜像
# 
# 环境区分逻辑：
# 1. 主服务器环境：
#    - 有 IMAGE_TAG 环境变量（从 docker/.env 读取，install.sh 写入）
#    - 需要 TASK_EXECUTOR_IMAGE 来创建 worker 容器执行任务
#    - 默认使用 {DOCKER_USER}/xingrin-worker:{IMAGE_TAG}（版本锁定）
#    - 开发时可设置 TASK_EXECUTOR_IMAGE=本地镜像名 覆盖
# 
# 2. Worker 容器环境：
#    - 无 IMAGE_TAG 环境变量（容器启动时未注入）
#    - 无 .env 文件
#    - 不需要 TASK_EXECUTOR_IMAGE（worker 只执行任务，不会再创建其他容器）
#    - 设为空字符串避免 AttributeError，确保 settings 模块正常加载
# 
# 开发环境配置示例：
# 在 docker/.env 中添加：TASK_EXECUTOR_IMAGE=docker-agent:test-v1.0.0
if IMAGE_TAG:
    # 主服务器场景：构建完整镜像名
    TASK_EXECUTOR_IMAGE = os.getenv('TASK_EXECUTOR_IMAGE', f'{DOCKER_USER}/xingrin-worker:{IMAGE_TAG}')
else:
    # Worker 容器场景：空值，防止加载错误
    TASK_EXECUTOR_IMAGE = os.getenv('TASK_EXECUTOR_IMAGE', '')

# 任务提交间隔（秒）- 防止短时间内重复分配到同一节点
# 应大于心跳间隔（3秒），确保负载数据已更新
TASK_SUBMIT_INTERVAL = int(os.getenv('TASK_SUBMIT_INTERVAL', '6'))

# 本地 Worker Docker 网络名称（与 docker-compose.yml 中定义的一致）
DOCKER_NETWORK_NAME = os.getenv('DOCKER_NETWORK_NAME', 'xingrin_network')

# 宿主机挂载源路径（所有节点统一使用固定路径）
# 部署前需创建：mkdir -p /opt/xingrin
HOST_RESULTS_DIR = '/opt/xingrin/results'
HOST_LOGS_DIR = '/opt/xingrin/logs'
HOST_FINGERPRINTS_DIR = '/opt/xingrin/fingerprints'
HOST_WORDLISTS_DIR = '/opt/xingrin/wordlists'

# ============================================
# Worker 配置中心（任务容器从 /api/workers/config/ 获取）
# ============================================
# Worker 数据库地址由 worker_views.py 的 config API 动态返回
# 根据请求来源（本地/远程）返回不同的配置：
# - 本地 Worker（Docker 网络内）：使用内部服务名 postgres
# - 远程 Worker（公网访问）：使用 PUBLIC_HOST
# 
# 注意：Redis 仅在 Server 容器内使用，Worker 不需要直接连接 Redis
_db_host = DATABASES['default']['HOST']
_is_internal_db = _db_host in ('postgres', 'localhost', '127.0.0.1')
WORKER_DB_HOST = os.getenv('WORKER_DB_HOST', _db_host)

# 容器内挂载目标路径（统一使用 /opt/xingrin）
CONTAINER_RESULTS_MOUNT = '/opt/xingrin/results'
CONTAINER_LOGS_MOUNT = '/opt/xingrin/logs'
