"""
Configuración común a todos los entornos.

Las capas dev y prod importan de acá y sólo cambian lo que difiere.
"""

from pathlib import Path

import environ

BASE_DIR = Path(__file__).resolve().parent.parent.parent

env = environ.Env()
environ.Env.read_env(BASE_DIR / '.env')

# --- Identidad de la instalación -------------------------------------------

SECRET_KEY = env('SECRET_KEY')
DEBUG = env.bool('DEBUG', default=False)
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=['localhost', '127.0.0.1'])

# --- Cifrado de credenciales -----------------------------------------------
#
# Sin esta clave no se pueden leer las credenciales guardadas, así que el
# arranque falla de forma explícita en vez de degradarse: operar sin poder
# consultar llenaría la base de estados desconocidos (R12).
SETTINGS_ENCRYPTION_KEY = env('SETTINGS_ENCRYPTION_KEY')

# Claves anteriores, para poder rotar sin detener el servicio: se cifra con la
# vigente y se descifra con cualquiera de la lista. Una rotación sin este
# respaldo deja ilegible todo lo guardado hasta ese momento.
SETTINGS_ENCRYPTION_KEY_FALLBACKS = env.list('SETTINGS_ENCRYPTION_KEY_FALLBACKS', default=[])

# --- Aplicaciones ----------------------------------------------------------

DJANGO_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]

THIRD_PARTY_APPS = [
    'rest_framework',
    'drf_spectacular',
    'django_vite',
    'inertia',
]

LOCAL_APPS = [
    'apps.core',
    'apps.accounts',
    'apps.billing',
    'apps.credentials',
    'apps.onboarding',
    'apps.domains',
    'apps.sitemaps',
    'apps.coverage',
    'apps.indexing',
    'apps.jobs',
    'apps.notifications',
    'apps.gsc',
    'apps.seo',
    'apps.web',
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    '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',
    'inertia.middleware.InertiaMiddleware',
    # Va después del de Inertia: comparte las props que toda página recibe
    # sin pedirlas, incluido el aviso de nivel cuenta que no puede faltar en
    # ninguna pantalla (FR-064).
    'apps.core.inertia.shared_props_middleware',
    # En desarrollo Django reemplaza el handler 404 por su informe amarillo.
    # Este middleware conserva ese informe, pero lo deja detrás del control de
    # depuración de la pantalla normal en vez de ocupar el documento entero.
    'apps.core.not_found.debug_not_found_middleware',
    # Anota la última actividad de la sesión en curso. Tiene que ir después del
    # de sesiones y del de autenticación: antes no existen ni la clave de sesión
    # ni el usuario, y no habría qué anotar.
    'apps.accounts.middleware.session_activity_middleware',
]

ROOT_URLCONF = 'config.urls'
WSGI_APPLICATION = 'config.wsgi.application'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'apps' / 'web' / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                # El idioma y el tema tienen que estar disponibles en la
                # plantilla base: el atributo `lang` y la clase del documento se
                # pintan ahí, antes de que exista una prop de Inertia.
                'apps.accounts.preferences.context',
            ],
        },
    },
]

# --- Almacenamiento --------------------------------------------------------

# El almacén definitivo es PostgreSQL. El respaldo en SQLite existe para poder
# arrancar y verificar el cableado sin levantar contenedores; producción exige
# DATABASE_URL de forma explícita y no acepta este respaldo.
DATABASES = {'default': env.db('DATABASE_URL', default=f'sqlite:///{BASE_DIR / "db.sqlite3"}')}
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# La cuenta es el usuario: no hay dos conceptos separados. Se declara desde la
# primera migración porque cambiarlo después obliga a rehacer el esquema.
AUTH_USER_MODEL = 'accounts.Account'

REDIS_URL = env('REDIS_URL', default='redis://localhost:6379/0')

# Adónde manda Django a quien no tiene sesión. Hay que declararlo: el valor de
# fábrica es «/accounts/login/», que en este proyecto no existe —el ingreso vive
# en «/login», sin prefijo de agrupación— y sin esta línea todo visitante
# anónimo termina en un 404 en vez de en el formulario.
LOGIN_URL = 'login'
LOGIN_REDIRECT_URL = 'domains'
LOGOUT_REDIRECT_URL = 'login'

# --- Sesiones abiertas -----------------------------------------------------
#
# El almacén de Django es la única verdad sobre qué sesión sigue abierta;
# `apps.accounts.models.Session` sólo agrega de quién es y desde dónde. Lo que
# sigue gobierna ese envoltorio.

# Cuánto dura una sesión sin actividad. Se declara aunque coincida con el valor
# de fábrica: es la ventana en la que una cookie robada sigue sirviendo, y una
# decisión de ese peso no puede quedar implícita.
SESSION_COOKIE_AGE = env.int('SESSION_COOKIE_AGE', default=60 * 60 * 24 * 14)

# Cada cuánto se escribe la última actividad. Bajarlo a cero convertiría cada
# navegación en un UPDATE sin decir nada nuevo: la pantalla muestra «hace unos
# minutos», no el segundo exacto.
SESSION_ACTIVITY_RESOLUTION_SECONDS = env.int('SESSION_ACTIVITY_RESOLUTION_SECONDS', default=300)

# Cuánto sobrevive la fila de una sesión ya cerrada. Guarda dirección de origen
# y agente de usuario —dato personal—, y se conservan porque la sospecha suele
# aparecer después del cierre. Pasado el plazo se borran (`purge_session_records`).
SESSION_RECORD_RETENTION_DAYS = env.int('SESSION_RECORD_RETENTION_DAYS', default=90)

# Si hay un proxy propio delante que reescribe `X-Forwarded-For`, se lo puede
# creer. Apagado por omisión: leerlo sin proxy deja que cualquiera declare la
# dirección que quiera y la lista de sesiones mienta.
TRUST_FORWARDED_FOR = env.bool('TRUST_FORWARDED_FOR', default=False)

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'},
]

# --- Internacionalización --------------------------------------------------

# El idioma fuente del producto es el inglés. Lo que ve el usuario no sale de
# acá —sale del catálogo del cliente, ver `frontend/lib/i18n.ts`— pero sí sale
# de acá lo que Django dice por su cuenta, y decirlo en un idioma y el resto en
# otro es peor que decirlo todo en uno.
LANGUAGE_CODE = 'en'
USE_I18N = True
USE_TZ = True

# Los instantes se guardan siempre en UTC.
TIME_ZONE = 'UTC'

# Zona en la que se presentan las fechas y en la que corta el día del
# presupuesto de cuota. Una fecha de obtención sin zona declarada es un dato
# ambiguo, y sobre ella se apoya la honestidad del producto (FR-065).
#
# Se declara por **nombre de zona** y nunca por diferencia horaria: «EST» es
# UTC-5 clavado todo el año, así que en marzo el día empezaría una hora tarde y
# el cupo se renovaría cuando nadie lo espera. `America/New_York` es EST en
# invierno y EDT en verano, y el cambio lo aplica sola la biblioteca de zonas.
DISPLAY_TIMEZONE = env('DISPLAY_TIMEZONE', default='America/New_York')

# --- Archivos estáticos ----------------------------------------------------

STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']

# --- Exportaciones ---------------------------------------------------------
#
# Los archivos que la exportación de cobertura arma en segundo plano. No cuelgan
# de STATIC_ROOT ni de una URL pública: el CSV lleva las URLs del sitio de una
# cuenta y se sirve por una vista que comprueba de quién es.
EXPORT_ROOT = Path(env('EXPORT_ROOT', default=str(BASE_DIR / 'var' / 'exports')))

# Cuánto sobrevive un archivo ya generado. Se borra por tiempo y no al
# descargarlo: quien lo pide de madrugada tiene que poder bajarlo al día
# siguiente, y quien lo baja dos veces no tiene por qué volver a esperar.
EXPORT_RETENTION_DAYS = env.int('EXPORT_RETENTION_DAYS', default=7)

# --- Inertia y Vite --------------------------------------------------------
#
# Inertia entrega los datos como props desde la vista, así que la interfaz no
# necesita una API paralela: la API pública queda para su propósito real, que
# es la integración desde un pipeline de despliegue (R9).
#
# La plantilla del paquete extiende INERTIA_LAYOUT y rellena su bloque
# «inertia» con el div de montaje y los datos de la página.
INERTIA_LAYOUT = 'base.html'

# En desarrollo los assets los sirve el servidor de Vite; en producción se
# resuelven por el manifiesto compilado. Lo distingue la configuración, no el
# código.
DJANGO_VITE = {
    'default': {
        'dev_mode': env.bool('DJANGO_VITE_DEV_MODE', default=False),
        # El protocolo se declara porque una página servida por HTTPS no puede
        # pedirle los assets a un servidor en HTTP plano: el navegador lo
        # bloquea por contenido mixto y la pantalla queda sin estilos ni
        # scripts, sin más explicación que un aviso en la consola.
        'dev_server_protocol': env('DJANGO_VITE_DEV_SERVER_PROTOCOL', default='http'),
        'dev_server_host': env('DJANGO_VITE_DEV_SERVER_HOST', default='localhost'),
        'dev_server_port': env.int('DJANGO_VITE_DEV_SERVER_PORT', default=5173),
        'static_url_prefix': 'dist',
        'manifest_path': BASE_DIR / 'static' / 'dist' / '.vite' / 'manifest.json',
    }
}

# --- Correo ----------------------------------------------------------------
#
# Django 6 configura el correo con MAILERS, no con EMAIL_BACKEND.
email_backend = env('EMAIL_BACKEND', default='django.core.mail.backends.console.EmailBackend')
MAILERS = {'default': {'BACKEND': email_backend}}
if email_backend == 'django.core.mail.backends.smtp.EmailBackend':
    MAILERS['default']['OPTIONS'] = {
        'host': env('EMAIL_HOST'),
        'port': env.int('EMAIL_PORT', default=587),
        'username': env('EMAIL_HOST_USER', default=''),
        'password': env('EMAIL_HOST_PASSWORD', default=''),
        'use_tls': env.bool('EMAIL_USE_TLS', default=True),
        'use_ssl': env.bool('EMAIL_USE_SSL', default=False),
        'timeout': env.int('EMAIL_TIMEOUT', default=10),
    }
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='Tools <no-reply@example.com>')

# --- Presupuesto de inspección ---------------------------------------------
#
# Límite publicado por Google, modelado como configuración para poder
# ajustarlo si Google lo cambia (principio II).
DEFAULT_DAILY_INSPECTION_BUDGET = env.int('DEFAULT_DAILY_INSPECTION_BUDGET', default=2000)
DEFAULT_MANUAL_RESERVE = env.int('DEFAULT_MANUAL_RESERVE', default=200)

# --- Capacidades diferidas -------------------------------------------------
#
# Cableadas y apagadas según el principio VI. Mientras estén apagadas no deben
# aparecer en ninguna pantalla, respuesta de la API ni correo.
BILLING_ENABLED = env.bool('BILLING_ENABLED', default=False)
TXT_VERIFICATION_ENABLED = env.bool('TXT_VERIFICATION_ENABLED', default=False)

# --- Caché -----------------------------------------------------------------
#
# Guarda las respuestas de idempotencia. En desarrollo alcanza la memoria del
# proceso; en producción es Redis, porque con varios procesos web una caché por
# proceso haría que el reintento caiga en uno que no vio el pedido original.
CACHES = {'default': env.cache('CACHE_URL', default='locmemcache://')}

# --- API pública -----------------------------------------------------------

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'apps.accounts.authentication.ApiKeyAuthentication',
        # La sesión sirve para poder recorrer la documentación desde el
        # navegador ya autenticado; las integraciones usan la clave.
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.IsAuthenticated'],
    'DEFAULT_SCHEMA_CLASS': 'drf_spectacular.openapi.AutoSchema',
    'EXCEPTION_HANDLER': 'apps.core.errors.api_exception_handler',
    'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer'],
    'UNAUTHENTICATED_USER': None,
}

SPECTACULAR_SETTINGS = {
    'TITLE': 'tools-patiospools',
    'DESCRIPTION': (
        'Sincronización de sitemaps con Google Search Console y monitoreo de la '
        'cobertura de indexación.'
    ),
    'VERSION': '1.0.0',
    'SERVE_INCLUDE_SCHEMA': False,
    'SCHEMA_PATH_PREFIX': '/api/v1',
    'COMPONENT_SPLIT_REQUEST': True,
}

# --- Tareas asíncronas -----------------------------------------------------
#
# Se reconoce la tarea al terminarla, no al recibirla: si un trabajador muere a
# mitad de un lote, el trabajo vuelve a la cola en vez de perderse. El precio es
# que una tarea puede correr dos veces, y por eso lo que gasta cuota pasa por el
# control de idempotencia.
CELERY_BROKER_URL = REDIS_URL
CELERY_RESULT_BACKEND = env('CELERY_RESULT_BACKEND', default=REDIS_URL)
CELERY_TASK_ACKS_LATE = True
CELERY_TASK_REJECT_ON_WORKER_LOST = True
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
CELERY_TASK_DEFAULT_QUEUE = 'default'
CELERY_TIMEZONE = DISPLAY_TIMEZONE
CELERY_TASK_ALWAYS_EAGER = env.bool('CELERY_TASK_ALWAYS_EAGER', default=False)

# --- Registro --------------------------------------------------------------
#
# El filtro que saca credenciales se aplica al manejador y no a un registrador
# concreto: un secreto puede salir por cualquiera, incluido el de una biblioteca
# ajena, y filtrar sólo los propios deja abierta justamente la parte que no
# controlamos.
LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'sin_credenciales': {'()': 'apps.core.logging.CredentialScrubFilter'},
    },
    'formatters': {
        'standard': {'format': '{levelname} {asctime} {name} {message}', 'style': '{'},
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',
            'formatter': 'standard',
            'filters': ['sin_credenciales'],
        },
    },
    'root': {'handlers': ['console'], 'level': env('LOG_LEVEL', default='INFO')},
}
