"""
Django settings for config project.

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

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

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

import os
from pathlib import Path

from dotenv import load_dotenv

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

load_dotenv(BASE_DIR / '.env')


def _env_bool(name: str, default: bool) -> bool:
    val = os.environ.get(name)
    if val is None:
        return default
    return val.strip().lower() in ('1', 'true', 'yes', 'on')


SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
if not SECRET_KEY:
    raise RuntimeError('DJANGO_SECRET_KEY env var is required')

DEBUG = _env_bool('DJANGO_DEBUG', False)

ALLOWED_HOSTS = [h.strip() for h in os.environ.get('DJANGO_ALLOWED_HOSTS', 'api.caridu.id,localhost,127.0.0.1').split(',') if h.strip()]


# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework',
    'corsheaders',
    'drf_spectacular',
    'channels',
    'axes',
    'apps.core',
    'apps.hr',
    'apps.projects',
    'apps.events',
    'apps.meetings',
    'apps.tasks',
    'apps.asset_management',
    'apps.procurements',
    'apps.administrations',
    'apps.publications',
    'apps.contacts',
    'apps.companies',
    'apps.activities',
    'apps.chat',
    'apps.tools',
    'apps.notifications',
    'apps.analytics',
    'apps.disseminations',
    'apps.finance',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    # GZip must run before any middleware that may set Vary or modify content
    'django.middleware.gzip.GZipMiddleware',
    # WhiteNoise serves STATIC_ROOT and (via subclass) MEDIA_ROOT directly from WSGI
    'config.whitenoise_media.WhiteNoiseWithMedia',
    'corsheaders.middleware.CorsMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    # AxesMiddleware must be last so it can record outcome of every request
    'axes.middleware.AxesMiddleware',
    # Analytics access log: capture public hits on tracked resources.
    'apps.analytics.middleware.AccessLogMiddleware',
]

# Public access analytics
ANALYTICS_ENABLED = _env_bool('ANALYTICS_ENABLED', True)
ANALYTICS_HASH_SALT = os.environ.get('ANALYTICS_HASH_SALT', '')  # falls back to SECRET_KEY
ANALYTICS_SKIP_PREFIXES = ['/admin/', '/api/schema', '/api/auth/']
# Absolute base URL the embeddable tracking tag points at (no trailing slash).
ANALYTICS_PUBLIC_BASE_URL = os.environ.get('ANALYTICS_PUBLIC_BASE_URL', 'http://localhost:8000')
# Number of trusted reverse-proxy hops in front of the app. 0 = ignore X-Forwarded-For
# (use REMOTE_ADDR) so clients can't spoof their IP. Set to match the deploy (e.g. 1 behind
# a single proxy, 2 behind Cloudflare->cPanel) to recover real client IPs for analytics/throttling.
TRUSTED_PROXY_COUNT = int(os.environ.get('TRUSTED_PROXY_COUNT', '0'))
# Only honor Cloudflare CF-IPCountry* headers when traffic is actually forced through Cloudflare.
BEHIND_CLOUDFLARE = _env_bool('BEHIND_CLOUDFLARE', False)
# Absolute base URL for public short links (the trackable link shared in posts).
# Defaults to the analytics base; short links resolve at <base>/api/urls/<code>/.
SHORTLINK_BASE_URL = os.environ.get('SHORTLINK_BASE_URL', ANALYTICS_PUBLIC_BASE_URL)

# Firebase Cloud Messaging (web push). Point FIREBASE_CREDENTIALS_FILE at a
# service-account JSON kept OUTSIDE the repo (e.g. ~/secrets/firebase.json on
# cPanel). Pushes are skipped silently when unset, so dev/CI run without creds.
FCM_ENABLED = _env_bool('FCM_ENABLED', True)
FIREBASE_CREDENTIALS_FILE = os.environ.get('FIREBASE_CREDENTIALS_FILE', '')

AUTHENTICATION_BACKENDS = [
    'axes.backends.AxesStandaloneBackend',
    'django.contrib.auth.backends.ModelBackend',
]

# Brute-force protection (django-axes)
AXES_FAILURE_LIMIT = 5
AXES_COOLOFF_TIME = 1  # hours
AXES_LOCKOUT_PARAMETERS = [['username', 'ip_address']]
AXES_RESET_ON_SUCCESS = True
AXES_LOCKOUT_CALLABLE = None
AXES_ENABLE_ADMIN = True
# Behind a reverse proxy in prod, set AXES_IPWARE_PROXY_COUNT or use X-Forwarded-For trust list
AXES_IPWARE_META_PRECEDENCE_ORDER = ['HTTP_X_FORWARDED_FOR', 'REMOTE_ADDR']

CORS_ALLOW_CREDENTIALS = True
CORS_ALLOWED_ORIGINS = [o.strip() for o in os.environ.get(
    'CORS_ALLOWED_ORIGINS',
    'https://caridu.id,https://www.caridu.id,https://api.caridu.id,http://localhost:3000,http://127.0.0.1:3000'
).split(',') if o.strip()]

# Cookie security: HTTPS-only in prod, HTTPOnly always, SameSite env-driven.
# Default cookie domain '.caridu.id' lets api.caridu.id and caridu.id share
# session/CSRF cookies. Override per env if needed (e.g. local dev → unset).
_default_cookie_domain = None if DEBUG else '.caridu.id'

SESSION_COOKIE_NAME = os.environ.get('SESSION_COOKIE_NAME', 'sessionid')
SESSION_COOKIE_SAMESITE = os.environ.get('SESSION_COOKIE_SAMESITE', 'Lax')
SESSION_COOKIE_SECURE = _env_bool('SESSION_COOKIE_SECURE', not DEBUG)
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_AGE = 60 * 60 * 24 * 14  # 14 days
SESSION_COOKIE_DOMAIN = os.environ.get('SESSION_COOKIE_DOMAIN') or _default_cookie_domain

CSRF_COOKIE_NAME = os.environ.get('CSRF_COOKIE_NAME', 'csrftoken')
CSRF_COOKIE_SAMESITE = os.environ.get('CSRF_COOKIE_SAMESITE', 'Lax')
CSRF_COOKIE_SECURE = _env_bool('CSRF_COOKIE_SECURE', not DEBUG)
CSRF_COOKIE_HTTPONLY = False  # JS must read for double-submit header
CSRF_COOKIE_DOMAIN = os.environ.get('CSRF_COOKIE_DOMAIN') or _default_cookie_domain

CSRF_TRUSTED_ORIGINS = [o.strip() for o in os.environ.get(
    'CSRF_TRUSTED_ORIGINS',
    'https://caridu.id,https://www.caridu.id,https://api.caridu.id,http://localhost:3000,http://localhost:8000,http://127.0.0.1:3000,http://127.0.0.1:8000'
).split(',') if o.strip()]

# HTTPS + reverse-proxy hardening.
# Production sits behind Apache/cPanel proxy: api.caridu.id → upstream WSGI.
# Trust X-Forwarded-* so Django builds absolute URLs and cookie domains using
# the public hostname, not the upstream's Host header.
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') if not DEBUG else None
USE_X_FORWARDED_HOST = not DEBUG
USE_X_FORWARDED_PORT = not DEBUG
SECURE_HSTS_SECONDS = 31536000 if not DEBUG else 0
SECURE_HSTS_INCLUDE_SUBDOMAINS = not DEBUG
SECURE_HSTS_PRELOAD = not DEBUG
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_REFERRER_POLICY = 'same-origin'
X_FRAME_OPTIONS = 'DENY'

REST_FRAMEWORK = {
    # HasMenuPermission is a no-op for ViewSets that don't set rbac_domain,
    # so tools/pdf_tools/chat/notifications/core endpoints stay open to any auth user.
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
        'apps.core.permissions.HasMenuPermission',
    ],
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_THROTTLE_CLASSES': [
        'rest_framework.throttling.AnonRateThrottle',
        'rest_framework.throttling.UserRateThrottle',
    ],
    'DEFAULT_THROTTLE_RATES': {
        'anon': '30/min',
        'user': '600/min',
        'login': '10/min',
    },
}

SPECTACULAR_SETTINGS = {
    'TITLE': 'KHub CRM API',
    'DESCRIPTION': 'CRM backend API with full feature coverage',
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
}

ROOT_URLCONF = 'config.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        '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_APPLICATION = 'config.asgi.application'

# Redis URL drives both the Channels layer and the cache below. Set it for
# local/dev (or any ASGI host with Redis); leave unset on prod cPanel (WSGI,
# no Redis) to fall back to in-memory backends.
_redis_url = os.environ.get('REDIS_URL', '').strip()

# Channel layer (WebSocket fan-out). Redis when REDIS_URL is set, else an
# in-memory layer so a host without Redis still boots.
if _redis_url:
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels_redis.core.RedisChannelLayer",
            "CONFIG": {"hosts": [_redis_url]},
        },
    }
else:
    CHANNEL_LAYERS = {
        "default": {"BACKEND": "channels.layers.InMemoryChannelLayer"},
    }


# Cache
# Use Redis when REDIS_URL is set (local/dev with a Redis server, or any host
# that provisions one). Falls back to per-process local memory otherwise so
# prod cPanel (WSGI-only, no Redis) keeps working unchanged.
# https://docs.djangoproject.com/en/6.0/topics/cache/
if _redis_url:
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.redis.RedisCache',
            'LOCATION': _redis_url,
            'KEY_PREFIX': 'khub',
        }
    }
else:
    CACHES = {
        'default': {
            'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
            'LOCATION': 'khub-locmem',
        }
    }


# Database
# https://docs.djangoproject.com/en/6.0/ref/settings/#databases

_db_engine = os.environ.get('DB_ENGINE', 'sqlite').lower()

# Persistent connections: reuse a DB connection for up to CONN_MAX_AGE seconds
# instead of opening one per request. Big latency win under load. Keep 0 only
# if the deploy can't hold open connections.
_conn_max_age = int(os.environ.get('DB_CONN_MAX_AGE', '60'))

if _db_engine in ('postgres', 'postgresql'):
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.postgresql',
            'NAME': os.environ['DB_NAME'],
            'USER': os.environ['DB_USER'],
            'PASSWORD': os.environ['DB_PASSWORD'],
            'HOST': os.environ.get('DB_HOST', 'localhost'),
            'PORT': os.environ.get('DB_PORT', '5432'),
            'CONN_MAX_AGE': _conn_max_age,
            # Reuse connections only while healthy (Django 4.1+).
            'CONN_HEALTH_CHECKS': True,
        }
    }
elif _db_engine == 'mysql':
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.mysql',
            'NAME': os.environ['DB_NAME'],
            'USER': os.environ['DB_USER'],
            'PASSWORD': os.environ['DB_PASSWORD'],
            'HOST': os.environ.get('DB_HOST', 'localhost'),
            'PORT': os.environ.get('DB_PORT', '3306'),
            'OPTIONS': {
                'charset': 'utf8mb4',
                'init_command': "SET sql_mode='STRICT_TRANS_TABLES'",
            },
            'CONN_MAX_AGE': _conn_max_age,
            'CONN_HEALTH_CHECKS': True,
        }
    }
else:
    # sqlite: CONN_MAX_AGE has little effect (file-based) but harmless.
    DATABASES = {
        'default': {
            'ENGINE': 'django.db.backends.sqlite3',
            'NAME': BASE_DIR / 'db.sqlite3',
        }
    }


# Password validation
# https://docs.djangoproject.com/en/6.0/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/6.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static + media files
# https://docs.djangoproject.com/en/6.0/howto/static-files/
# Apache serves STATIC_ROOT and MEDIA_ROOT directly from
# /home/csis3web/network.csis.or.id/{static,media}.

STATIC_URL = os.environ.get('DJANGO_STATIC_URL', '/static/')
STATIC_ROOT = os.environ.get(
    'DJANGO_STATIC_ROOT',
    '/home/csis3web/network.csis.or.id/static',
)

MEDIA_URL = os.environ.get('DJANGO_MEDIA_URL', '/media/')
MEDIA_ROOT = os.environ.get(
    'DJANGO_MEDIA_ROOT',
    '/home/csis3web/network.csis.or.id/media',
)

# Email. Defaults to console backend (prints to stdout) for dev; override
# DJANGO_EMAIL_BACKEND + SMTP env vars in production.
EMAIL_BACKEND = os.environ.get(
    'DJANGO_EMAIL_BACKEND',
    'django.core.mail.backends.console.EmailBackend',
)
EMAIL_HOST = os.environ.get('DJANGO_EMAIL_HOST', 'localhost')
EMAIL_PORT = int(os.environ.get('DJANGO_EMAIL_PORT', '587'))
EMAIL_USE_TLS = os.environ.get('DJANGO_EMAIL_USE_TLS', 'true').lower() in ('1', 'true', 'yes')
EMAIL_HOST_USER = os.environ.get('DJANGO_EMAIL_HOST_USER', '')
EMAIL_HOST_PASSWORD = os.environ.get('DJANGO_EMAIL_HOST_PASSWORD', '')
DEFAULT_FROM_EMAIL = os.environ.get('DJANGO_DEFAULT_FROM_EMAIL', 'Karajo Hub <no-reply@karajohub.local>')

# WhiteNoise (static + media via WhiteNoiseWithMedia subclass).
# AUTOREFRESH: rescan disk per request so newly uploaded media is served
# without a worker restart. Negligible overhead for bootstrap traffic.
WHITENOISE_AUTOREFRESH = True
WHITENOISE_USE_FINDERS = DEBUG
# Don't crash if a hashed asset is missing from the manifest (templates may
# reference legacy paths during deploys). Prod-resilient default.
WHITENOISE_MANIFEST_STRICT = False
# 1-year cache for hashed static assets; WhiteNoise auto-disables for media
# (non-hashed) and serves with conservative defaults.
WHITENOISE_MAX_AGE = 31536000 if not DEBUG else 0

# Django 6 STORAGES: production-grade static pipeline.
# CompressedManifestStaticFilesStorage hashes filenames + gzip/brotli pre-compresses.
STORAGES = {
    'default': {
        'BACKEND': 'django.core.files.storage.FileSystemStorage',
    },
    'staticfiles': {
        'BACKEND': (
            'whitenoise.storage.CompressedManifestStaticFilesStorage'
            if not DEBUG
            else 'django.contrib.staticfiles.storage.StaticFilesStorage'
        ),
    },
}

# Upload limits: cap multipart body + per-file size to mitigate DoS via huge uploads
DATA_UPLOAD_MAX_MEMORY_SIZE = 25 * 1024 * 1024   # 25 MB total request body
FILE_UPLOAD_MAX_MEMORY_SIZE = 25 * 1024 * 1024   # 25 MB per file in memory
DATA_UPLOAD_MAX_NUMBER_FIELDS = 1000

# App-level: hard cap per attachment file (50 MB).
# Used by views before persisting. Tune per deployment via env.
ATTACHMENT_MAX_FILE_SIZE = int(os.environ.get('ATTACHMENT_MAX_FILE_SIZE', 50 * 1024 * 1024))
ATTACHMENT_ALLOWED_MIME_PREFIXES = (
    'image/', 'application/pdf', 'application/zip',
    'application/vnd.openxmlformats-officedocument.',
    'application/msword', 'application/vnd.ms-excel', 'application/vnd.ms-powerpoint',
    'text/', 'audio/', 'video/',
)

AUTH_USER_MODEL = 'core.User'

# AWS Rekognition (HR attendance face recognition)
AWS_ACCESS_KEY_ID = os.environ.get('AWS_ACCESS_KEY_ID', '')
AWS_SECRET_ACCESS_KEY = os.environ.get('AWS_SECRET_ACCESS_KEY', '')
AWS_REGION = os.environ.get('AWS_REGION', 'ap-southeast-1')
REKOGNITION_COLLECTION_ID = os.environ.get('REKOGNITION_COLLECTION_ID', 'khub-attendance')
REKOGNITION_FACE_MATCH_THRESHOLD = float(os.environ.get('REKOGNITION_FACE_MATCH_THRESHOLD', '90'))

# Microsoft Azure AD / Office 365 OAuth
MICROSOFT_CLIENT_ID = os.environ.get('MICROSOFT_CLIENT_ID', '')
MICROSOFT_CLIENT_SECRET = os.environ.get('MICROSOFT_CLIENT_SECRET', '')
MICROSOFT_TENANT = os.environ.get('MICROSOFT_TENANT', 'common')
MICROSOFT_REDIRECT_URI = os.environ.get(
    'MICROSOFT_REDIRECT_URI',
    'http://localhost:8000/api/auth/microsoft/callback/'
)
FRONTEND_URL = os.environ.get('FRONTEND_URL', 'http://localhost:3000')

# DeepSeek AI (OpenAI-compatible) — project description assistant
DEEPSEEK_API_KEY = os.environ.get('DEEPSEEK_API_KEY', '')
DEEPSEEK_BASE_URL = os.environ.get('DEEPSEEK_BASE_URL', 'https://api.deepseek.com')
DEEPSEEK_MODEL = os.environ.get('DEEPSEEK_MODEL', 'deepseek-chat')
