"""
Base settings for The Panel.
All environments inherit from this.
"""
# Use pysqlite3-binary (newer SQLite) if available — required on servers
# with SQLite < 3.31 (e.g. AlmaLinux 8 ships 3.26)
try:
    import pysqlite3
    import sys
    sys.modules['sqlite3'] = sys.modules.pop('pysqlite3')
except ImportError:
    pass

import os
from pathlib import Path
import environ

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

# ─── CMS Version ──────────────────────────────────────────────────────────────
# Format: YY.M.V  (year . month . release number)
# One number across the whole estate. Fleet compares this against the
# target_version it sends, so a separate product numbering would make that
# comparison meaningless. Change it once per release, the context processor
# injects it everywhere.
CMS_VERSION = "26.11.1"   # plain literal: env() is not built yet at this line

env = environ.Env(DEBUG=(bool, False))
environ.Env.read_env(os.path.join(BASE_DIR, '.env'))

SECRET_KEY = env('SECRET_KEY')
ALLOWED_HOSTS = env.list('ALLOWED_HOSTS', default=[])

# Public-facing base URL used in outgoing emails (no trailing slash)
SITE_URL = env('SITE_URL', default='http://localhost:8000')

# Portfolio thumbnail crop, WIDTHxHEIGHT. Portrait by default, which suits
# Final Target's project cards. A site with landscape brand cards sets its own
# in .env, for example PORTFOLIO_THUMB=600x380, and nothing else has to change.
PORTFOLIO_THUMB = env('PORTFOLIO_THUMB', default='600x800')

# Panel subdomain, override per deployment in .env (e.g. panel.example.com)
PANEL_HOST = env('PANEL_HOST', default='localhost')

# The systemd unit for THIS install. Restores and restarts use it, so an empty
# value means the panel refuses to restart anything rather than restarting a
# service belonging to a different site on the same server.
SERVICE_NAME = env('SERVICE_NAME', default='')

# Hosting. Facts about the machine this install runs on, which nothing on the
# server can work out for itself: who it is bought from, what the plan is
# called, and when the agreement ends. They sit here with the other
# install-level settings rather than on the Tenant model, because they describe
# the box rather than the client, and because a settings read cannot take the
# site down the way an unapplied column can.
HOSTING_PROVIDER = env('HOSTING_PROVIDER', default='')
HOSTING_PACKAGE  = env('HOSTING_PACKAGE',  default='')
HOSTING_TYPE     = env('HOSTING_TYPE',     default='')   # e.g. Dedicated Server
HOSTING_IP       = env('HOSTING_IP',       default='')
HOSTING_ENDS_ON  = env('HOSTING_ENDS_ON',  default='')   # YYYY-MM-DD
HOSTING_STARTED_ON = env('HOSTING_STARTED_ON', default='')  # YYYY-MM-DD
# What the account was sold, when neither number can be measured from
# inside it. A reseller sees the whole partition, so the panel reported
# 93 GB of 291 GB against a 50 GB entitlement.
HOSTING_QUOTA_GB = env('HOSTING_QUOTA_GB', default='')
HOSTING_USED_GB  = env('HOSTING_USED_GB',  default='')

# The domain itself. This is the one on the page with no second chance: a
# certificate that lapses is an afternoon, a domain that lapses goes to
# redemption and then to whoever wants it. Read from here for now; if RDAP
# answers for the TLD these become the fallback rather than the source.
DOMAIN_REGISTRAR   = env('DOMAIN_REGISTRAR',   default='')
DOMAIN_OWNER       = env('DOMAIN_OWNER',       default='')
DOMAIN_EXPIRES_ON  = env('DOMAIN_EXPIRES_ON',  default='')   # YYYY-MM-DD

# When this panel's subscription runs out. The tenant row wins if it carries a
# renewal date, because that is what billing works from. This is the fallback
# for an install whose billing lives somewhere else, which includes the master.
PANEL_EXPIRES_ON   = env('PANEL_EXPIRES_ON',   default='')   # YYYY-MM-DD

# ─── Installed Apps ────────────────────────────────────────────────────────────
DJANGO_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'django.contrib.humanize',
]

THIRD_PARTY_APPS = [
    'rest_framework',
    'crispy_forms',
    'crispy_bootstrap5',
    'django_ckeditor_5',
    'social_django',
    'storages',
    'encrypted_model_fields',
]

LOCAL_APPS = [
    'apps.tenants',    # ← must come before other local apps (FK target)
    'apps.accounts',
    'apps.pages',
    'apps.portfolio',
    'apps.insights',
    'apps.auction',
    'apps.marketplace',
    'apps.tickets',
    'apps.contact',
    'apps.faq',
    'apps.chat',
    'apps.fab_auth',    # FAB Club OAuth integration
    'apps.sales',       # Sales pipeline, domain inquiries
    'apps.ecommerce',   # Products, orders, Stripe payments
    'apps.media',       # Media Hub — video/audio library with HLS streaming
    'apps.brands',      # Sub-brands of a group, each served at /<slug>/
    'apps.ftlink',      # Fleet control: tenant check-in and directives
    'apps.veventa',     # Events ERP: companies, brands, events, scoring
]

INSTALLED_APPS = DJANGO_APPS + THIRD_PARTY_APPS + LOCAL_APPS

# ─── Fleet control ──────────────────────────────────────────────────────────────
# Set on tenant installs only. The master install leaves FT_CONTROL_URL empty so
# it never checks in to itself.
FT_CONTROL_URL  = env('FT_CONTROL_URL', default='')
# Optional. When the tenant shares a server with the fleet master, point
# FT_CONTROL_URL at 127.0.0.1 and put the public hostname here, so the check-in
# stays on the machine and never passes through Cloudflare.
FT_CONTROL_HOST = env('FT_CONTROL_HOST', default='')
FT_TENANT_SLUG  = env('FT_TENANT_SLUG', default='')
FT_TENANT_KEY   = env('FT_TENANT_KEY', default='')

# ─── Middleware ─────────────────────────────────────────────────────────────────
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'apps.tenants.middleware.RateLimitMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',
    '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',
    'social_django.middleware.SocialAuthExceptionMiddleware',
    # Panel subdomain, mounts the CMS at / on PANEL_HOST and hides /cms/ from the URL
    'apps.tenants.middleware.PanelSubdomainMiddleware',
    # Multi-tenant detection — runs after session/auth so request.user is available
    'apps.tenants.middleware.TenantMiddleware',
    # Role enforcement — refuses CMS sections a role may not open
    'apps.accounts.middleware.RolePermissionMiddleware',
]

ROOT_URLCONF = 'config.urls'

# ─── Templates ──────────────────────────────────────────────────────────────────
# Which public design this install wears. One install serves one site, so the
# theme is a property of the install rather than of the request.
#
#   starter      blank white, the site name and nothing else, what a new
#                install gets until somebody chooses a design
#   finaltarget  the original agency design, which used to be the only one
#                and is why a fresh install came up looking like Final Target
#
# A theme only needs to hold the files it overrides. Anything it does not
# carry falls through to templates/, so a theme is a handful of files rather
# than a copy of the whole site.
SITE_THEME = os.environ.get('SITE_THEME', 'starter').strip() or 'starter'
_THEME_DIR = BASE_DIR / 'templates' / 'themes' / SITE_THEME

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ([_THEME_DIR] if _THEME_DIR.is_dir() else []) + [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
                'social_django.context_processors.backends',
                'social_django.context_processors.login_redirect',
                'apps.pages.context_processors.global_settings',
                'apps.tenants.context_processors.tenant_context',
                'apps.accounts.context_processors.cms_notifications',
                'apps.accounts.context_processors.role_permissions',
            ],
        },
    },
]

WSGI_APPLICATION = 'config.wsgi.application'

# ─── Database ───────────────────────────────────────────────────────────────────
DATABASES = {
    'default': env.db('DATABASE_URL', default='postgresql://localhost/panel')
}

# ─── Auth ───────────────────────────────────────────────────────────────────────
AUTH_USER_MODEL = 'accounts.User'

AUTHENTICATION_BACKENDS = [
    'apps.fab_auth.backend.FABClubOAuth2',      # FAB Club OAuth SSO
    'django.contrib.auth.backends.ModelBackend',
]

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

LOGIN_URL = '/cms/login/'
LOGIN_REDIRECT_URL = '/cms/'
LOGOUT_REDIRECT_URL = '/'

# ─── FAB Club OAuth ─────────────────────────────────────────────────────────────
# The client id and secret live in Settings, encrypted, and are read by
# apps.fab_auth.backend.FABClubOAuth2.get_key_and_secret. These two are left
# blank on purpose: social-auth falls back to them only when Settings is empty.
FAB_OAUTH_BASE_URL         = env('FAB_OAUTH_BASE_URL', default='https://fab.club')
SOCIAL_AUTH_FABCLUB_KEY    = ''
SOCIAL_AUTH_FABCLUB_SECRET = ''
SOCIAL_AUTH_FABCLUB_SCOPE  = ['openid', 'profile', 'email', 'points']

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.social_user',
    'social_core.pipeline.user.get_username',
    'social_core.pipeline.user.create_user',
    'social_core.pipeline.social_auth.associate_user',
    'social_core.pipeline.social_auth.load_extra_data',
    'social_core.pipeline.user.user_details',
    # FAB-specific: persist tier/points, attach to tenant
    'apps.fab_auth.pipeline.save_fab_profile',
    'apps.fab_auth.pipeline.attach_tenant_user',
)

# Where to redirect after social auth login/error
SOCIAL_AUTH_LOGIN_REDIRECT_URL  = '/'
SOCIAL_AUTH_LOGIN_ERROR_URL     = '/login/'
SOCIAL_AUTH_NEW_USER_REDIRECT_URL = '/'

# ─── Internationalization ───────────────────────────────────────────────────────
LANGUAGE_CODE = 'en-gb'
TIME_ZONE = 'Europe/London'
USE_I18N = True
USE_TZ = True

# ─── Static & Media ─────────────────────────────────────────────────────────────
STATIC_URL = '/static/'
STATIC_ROOT = BASE_DIR / 'staticfiles'
STATICFILES_DIRS = [BASE_DIR / 'static']
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

MEDIA_URL = '/media/'
MEDIA_ROOT = BASE_DIR / 'media'

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

# ─── Crispy Forms ───────────────────────────────────────────────────────────────
CRISPY_ALLOWED_TEMPLATE_PACKS = 'bootstrap5'
CRISPY_TEMPLATE_PACK = 'bootstrap5'

# ─── CKEditor 5 ────────────────────────────────────────────────────────────────
CKEDITOR_5_CONFIGS = {
    'default': {
        'toolbar': [
            'heading', '|', 'bold', 'italic', 'underline', 'strikethrough', '|',
            'alignment', '|',
            'link', 'bulletedList', 'numberedList', 'blockQuote', '|',
            'imageUpload', 'mediaEmbed', '|', 'undo', 'redo',
        ],
        'alignment': {
            'options': ['left', 'right', 'center', 'justify'],
        },
        # A link to another site opens in a new tab on its own, and the link
        # dialog gains a toggle for the cases you want to decide yourself.
        # Without this there is nowhere in the editor to set target, so it
        # gets typed into the address and the link breaks.
        'link': {
            'addTargetToExternalLinks': True,
            'decorators': {
                'openInNewTab': {
                    'mode': 'manual',
                    'label': 'Open in a new tab',
                    'attributes': {'target': '_blank', 'rel': 'noopener noreferrer'},
                },
            },
        },
    },
    'extended': {
        'toolbar': [
            'heading', '|', 'bold', 'italic', 'underline', 'strikethrough',
            'highlight', 'fontColor', 'fontBackgroundColor', '|',
            'alignment', '|', 'link', 'bulletedList', 'numberedList',
            'todoList', 'blockQuote', '|', 'insertTable', 'tableColumn',
            'tableRow', 'mergeTableCells', '|', 'imageUpload', 'mediaEmbed',
            'htmlEmbed', '|', 'sourceEditing', '|', 'undo', 'redo',
        ],
        'alignment': {
            'options': ['left', 'right', 'center', 'justify'],
        },
        # A link to another site opens in a new tab on its own, and the link
        # dialog gains a toggle for the cases you want to decide yourself.
        # Without this there is nowhere in the editor to set target, so it
        # gets typed into the address and the link breaks.
        'link': {
            'addTargetToExternalLinks': True,
            'decorators': {
                'openInNewTab': {
                    'mode': 'manual',
                    'label': 'Open in a new tab',
                    'attributes': {'target': '_blank', 'rel': 'noopener noreferrer'},
                },
            },
        },
        'fontColor': {
            'colors': [
                {'color': '#FCFCFC', 'label': 'White'},
                {'color': '#daf0fa', 'label': 'Light Blue'},
                {'color': '#ff404b', 'label': 'Red (Accent)'},
                {'color': '#16517b', 'label': 'Blue'},
                {'color': '#033152', 'label': 'Dark Blue'},
                {'color': '#121212', 'label': 'Black'},
                {'color': '#FFD700', 'label': 'Gold'},
            ],
            'columns': 4,
        },
        'fontBackgroundColor': {
            'colors': [
                {'color': '#033152', 'label': 'Dark Blue'},
                {'color': '#16517b', 'label': 'Blue'},
                {'color': '#ff404b', 'label': 'Red'},
                {'color': '#FCFCFC', 'label': 'White'},
                {'color': '#121212', 'label': 'Black'},
                {'color': 'transparent', 'label': 'None'},
            ],
            'columns': 4,
        },
    },
}

# (reCAPTCHA settings are defined further below — do not duplicate here)

# ─── Vault Encryption Key ────────────────────────────────────────────────────────
# Generate with: python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"
# This key encrypts all sensitive DB fields (Stripe, Twilio, OpenAI, reCAPTCHA keys).
# NEVER commit this key. NEVER lose it — you cannot decrypt existing data without it.
FIELD_ENCRYPTION_KEY = env('FIELD_ENCRYPTION_KEY', default='')

# ─── Stripe ─────────────────────────────────────────────────────────────────────
# Removed on 22 August 2026. Every one of these now comes from the
# Settings row, encrypted, and .env carries no credential at all.
# Read them with apps.pages.api_keys and apps.ecommerce.stripe_utils.

# ─── Email ──────────────────────────────────────────────────────────────────────
EMAIL_BACKEND = env('EMAIL_BACKEND', default='apps.pages.email_backend.SiteSettingsEmailBackend')
EMAIL_HOST = env('EMAIL_HOST', default='')
EMAIL_PORT = env.int('EMAIL_PORT', default=587)
EMAIL_USE_TLS = env.bool('EMAIL_USE_TLS', default=True)
EMAIL_USE_SSL = env.bool('EMAIL_USE_SSL', default=False)
# Mailbox and password come from Settings, read by SiteSettingsEmailBackend.
# Blank on purpose. An install that has not been configured must not send
# mail wearing another site's address.
DEFAULT_FROM_EMAIL = env('DEFAULT_FROM_EMAIL', default='')
ADMIN_EMAIL = env('ADMIN_EMAIL', default='')

# ─── Redis / Celery ─────────────────────────────────────────────────────────────
REDIS_URL = env('REDIS_URL', default='redis://localhost:6379/0')

CELERY_BROKER_URL = REDIS_URL
CELERY_RESULT_BACKEND = REDIS_URL
CELERY_ACCEPT_CONTENT = ['json']
CELERY_TASK_SERIALIZER = 'json'

CACHES = {
    'default': {
        'BACKEND': 'django_redis.cache.RedisCache',
        'LOCATION': REDIS_URL,
        'OPTIONS': {'CLIENT_CLASS': 'django_redis.client.DefaultClient'},
    }
}

SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
SESSION_CACHE_ALIAS = 'default'

# ─── REST Framework ─────────────────────────────────────────────────────────────
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': [
        'rest_framework.authentication.SessionAuthentication',
    ],
    'DEFAULT_PERMISSION_CLASSES': [
        'rest_framework.permissions.IsAuthenticated',
    ],
}

# ─── Auction page output path ───────────────────────────────────────────────────
AUCTION_PAGES_ROOT = env('AUCTION_PAGES_ROOT', default=str(BASE_DIR.parent / 'auction'))
AUCTION_PAGES_URL_PREFIX = '/auction/'

# ─── Esendex (SMS OTP) — formerly VoodooSMS / Commify ───────────────────────────
# Primary Key from https://console.esendex.com/developer
ESENDEX_API_KEY     = env('ESENDEX_API_KEY', default='')
# Account reference shown in the Esendex portal, e.g. EX0123456
ESENDEX_ACCOUNT_REF = env('ESENDEX_ACCOUNT_REF', default='')
# Sender name shown on the recipient's phone (alphanumeric, max ~11 chars)
ESENDEX_SENDER_ID   = env('ESENDEX_SENDER_ID', default='')

# ─── reCAPTCHA ──────────────────────────────────────────────────────────────────
# Both pairs live in Settings. This is the only value left here, because a
# score threshold is a tuning number, not a credential.
RECAPTCHA_MINIMUM_SCORE = 0.5

# ─── OpenAI (AI Chat) ────────────────────────────────────────────────────────
# Key lives in Settings. apps.pages.api_keys.get_openai_api_key reads it.

# ─── Namecheap Domain Reseller API ──────────────────────────────────────────
NAMECHEAP_API_USER    = env('NAMECHEAP_API_USER', default='')
NAMECHEAP_API_KEY     = env('NAMECHEAP_API_KEY', default='')
NAMECHEAP_USERNAME    = env('NAMECHEAP_USERNAME', default='')
NAMECHEAP_CLIENT_IP   = env('NAMECHEAP_CLIENT_IP', default='')
NAMECHEAP_SANDBOX     = env.bool('NAMECHEAP_SANDBOX', default=True)  # set False in production

# ─── Google Analytics 4 ─────────────────────────────────────────────────────
GA_PROPERTY_ID      = env('GA_PROPERTY_ID', default='')
# Store ga_credentials.json OUTSIDE the repo (e.g. /etc/secrets/ga_credentials.json)
