from pathlib import Path
import os
from datetime import timedelta

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

SECRET_KEY = os.getenv("SECRET_KEY", "dev-secret-key-change-me")
DEBUG = os.getenv("DEBUG", "False").lower() == "true"
ALLOWED_HOSTS = [
    h.strip()
    # iilmp.local/web/host.docker.internal let the Moodle container reach IILMP for
    # the SSO token/userinfo exchange (dev). Prod sets ALLOWED_HOSTS via env.
    for h in os.getenv("ALLOWED_HOSTS", "127.0.0.1,localhost,iilmp.local,web,host.docker.internal").split(",")
    if h.strip()
]

# Absolute URL for email links (no trailing slash)
PUBLIC_APP_BASE_URL = os.getenv("PUBLIC_APP_BASE_URL", "http://127.0.0.1:8000").rstrip("/")
ALUMNI_WEBHOOK_URL = os.getenv("ALUMNI_WEBHOOK_URL", "").strip()
ALUMNI_WEBHOOK_SECRET = os.getenv("ALUMNI_WEBHOOK_SECRET", "").strip()

# §4.3.5 FRALU020 — read-only social-media embeds on the alumni surfaces.
# Set ALUMNI_SOCIAL_EMBEDS_ENABLED=false in test/CI envs to skip third-party scripts.
ALUMNI_SOCIAL_EMBEDS = {
    "linkedin_company_url": os.getenv(
        "ALUMNI_LINKEDIN_COMPANY_URL", "https://www.linkedin.com/company/ruforum/"
    ),
    "twitter_handle": os.getenv("ALUMNI_TWITTER_HANDLE", "RUFORUMNetwork"),
    "enabled": os.getenv("ALUMNI_SOCIAL_EMBEDS_ENABLED", "true").lower() == "true",
}

# §4.3.5 — LinkedIn one-time OAuth pull (Sign In with LinkedIn / OIDC).
# Leave the client id/secret blank in dev to disable the feature; the UI
# only renders the "Connect LinkedIn" button when both are set.
LINKEDIN_CLIENT_ID = os.getenv("LINKEDIN_CLIENT_ID", "").strip()
LINKEDIN_CLIENT_SECRET = os.getenv("LINKEDIN_CLIENT_SECRET", "").strip()
LINKEDIN_OAUTH_REDIRECT_URL = os.getenv("LINKEDIN_OAUTH_REDIRECT_URL", "").strip()
# WS1.2 — REP→M&EL push (FRREP-AR009). When unset, the drain task is a no-op
# instead of silently flipping rows to SENT — this makes a misconfigured env
# visible.
MEL_WEBHOOK_URL = os.getenv("MEL_WEBHOOK_URL", "").strip()
MEL_WEBHOOK_SECRET = os.getenv("MEL_WEBHOOK_SECRET", "").strip()

# WS4.1 / FRREP-LD003 — BigBlueButton API.
# Default to the BBB project's public test instance so dev environments work
# out of the box. Production must set BBB_URL + BBB_SHARED_SECRET via env.
BBB_URL = os.getenv("BBB_URL", "https://test-install.blindsidenetworks.com/bigbluebutton/").strip()
BBB_SHARED_SECRET = os.getenv("BBB_SHARED_SECRET", "8cd8ef52e8e101574e400365b55e11a6").strip()
# bbb-webhooks plugin secret. Independent of BBB_SHARED_SECRET so the
# operator can rotate webhook delivery auth without rotating the API
# secret. Empty in dev; required for the inbound webhook receiver to
# accept any deliveries (returns 503 otherwise).
BBB_WEBHOOK_SECRET = os.getenv("BBB_WEBHOOK_SECRET", "").strip()
# WS1.1 — RIMS→REP auto-enrol HMAC (FRREP-EN003).
RIMS_REP_HMAC_SECRET = os.getenv("RIMS_REP_HMAC_SECRET", "").strip()

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django.contrib.humanize",
    "rest_framework",
    "rest_framework_simplejwt",
    "rest_framework_simplejwt.token_blacklist",
    "drf_spectacular",
    "corsheaders",
    "django_filters",
    "django_htmx",
    # OAuth2/OIDC provider — IILMP is the IdP for "Log in with IILMP" in Moodle.
    "oauth2_provider",
    "guardian",
    "django_celery_beat",
    "django_celery_results",
    "simple_history",
    "apps.core",
    "apps.rims.operations",
    "apps.rims.grants",
    "apps.rims.finance",
    "apps.rims.projects",
    "apps.rims.scholarships",
    "apps.rims.hr",
    "apps.rims.fleet",
    "apps.rims.contacts",
    "apps.rims.calendar",
    # REP learning platform (11 apps) retired in favour of Moodle — see the
    # Moodle docker-compose service + the Moodle→MEL connector.
    "apps.repository.documents",
    "apps.repository.analytics",
    "apps.mel.indicators",
    "apps.mel.tracking",
    "apps.mel.reports",
    "apps.mel.feedback",
    "apps.alumni.profiles",
    "apps.alumni.tracking",
    "apps.alumni.engagement",
    "apps.alumni.jobs",
    # Job board / CV builder / career resources (moved out of apps.rep; app
    # label stays ``rep_career`` so the existing tables/permissions are reused).
    "apps.alumni.careers",
    "apps.alumni.recognition",
    "apps.smehub.onboarding",
    "apps.smehub.incubation",
    "apps.smehub.linkage",
    "apps.smehub.marketplace",
    "apps.smehub.showcasing",
    "apps.smehub.investment",
    "axes",
]

X_FRAME_OPTIONS = "SAMEORIGIN"

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    # WS5.12 — LocaleMiddleware must sit between SessionMiddleware and
    # CommonMiddleware so request.LANGUAGE_CODE resolves before any view runs.
    "django.middleware.locale.LocaleMiddleware",
    "corsheaders.middleware.CorsMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "axes.middleware.AxesMiddleware",
    "simple_history.middleware.HistoryRequestMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "apps.core.middleware.SessionIdleTimeoutMiddleware",
    "apps.core.middleware.MFAEnforcementMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "django_htmx.middleware.HtmxMiddleware",
    "apps.core.middleware.HtmxAuthRedirectMiddleware",
    "apps.core.audit.middleware.AuditMiddleware",
]

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.debug",
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
                # WS5.12 — exposes LANGUAGES + LANGUAGE_CODE to templates so
                # the language switcher can iterate the active set.
                "django.template.context_processors.i18n",
                "apps.alumni.context_processors.alumni_social_embeds",
                # Sidebar module visibility flags based on user role
                "apps.core.nav_context.nav_visibility",
                # Real unread-notification count for the topnav bell badge
                "apps.core.nav_context.nav_notifications",
                # Single source of truth for cross-module colour tokens
                # consumed by tracking_dashboard.html and the report
                # builder JS (apps.mel.tracking.constants.MODULE_COLORS).
                "apps.mel.tracking.context.module_colors",
            ],
        },
    },
]

WSGI_APPLICATION = "config.wsgi.application"
ASGI_APPLICATION = "config.asgi.application"

DATABASES = {
    "default": {
        "ENGINE": os.getenv("DB_ENGINE", "django.db.backends.postgresql"),
        "NAME": os.getenv("DB_NAME", "iilmp"),
        "USER": os.getenv("DB_USER", "iilmp"),
        "PASSWORD": os.getenv("DB_PASSWORD", "iilmp"),
        "HOST": os.getenv("DB_HOST", "localhost"),
        "PORT": os.getenv("DB_PORT", "5432"),
        "CONN_MAX_AGE": int(os.getenv("DB_CONN_MAX_AGE", "0")),
    }
}

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

LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"
USE_I18N = True
USE_TZ = True

# WS5.12 / contract requirement — French, Arabic, Portuguese activation alongside
# English. The framework is wired here; .po translation files start as
# placeholders and are filled in over time. `LOCALE_PATHS` points at the
# project-level locale dir so apps share one translation tree rather than each
# carrying a private `locale/`.
from django.utils.translation import gettext_lazy as _

LANGUAGES = [
    ("en", _("English")),
    ("fr", _("Français")),
    ("ar", _("العربية")),
    ("pt", _("Português")),
]
LOCALE_PATHS = [BASE_DIR / "locale"]

STATIC_URL = "/static/"
STATICFILES_DIRS = [BASE_DIR / "static"]
STATIC_ROOT = BASE_DIR / "staticfiles"
STORAGES = {
    "default": {
        "BACKEND": "django.core.files.storage.FileSystemStorage",
    },
    "staticfiles": {
        "BACKEND": "apps.core.storage.static_storage.LooseManifestStaticFilesStorage",
    },
}

MEDIA_URL = "/uploads/"
MEDIA_ROOT = BASE_DIR / "uploads"
FILE_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024
DATA_UPLOAD_MAX_MEMORY_SIZE = 10 * 1024 * 1024
FILE_UPLOAD_PERMISSIONS = 0o644
FILE_UPLOAD_DIRECTORY_PERMISSIONS = 0o755

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
AUTH_USER_MODEL = "core.CustomUser"

AUTHENTICATION_BACKENDS = (
    # axes backend MUST come first per https://django-axes.readthedocs.io/
    "axes.backends.AxesStandaloneBackend",
    "apps.core.authentication.backends.EmailBackend",
    "django.contrib.auth.backends.ModelBackend",
    # OAuth2 bearer-token backend (after password backends so interactive login
    # still flows through axes + EmailBackend, i.e. MFA is preserved).
    "oauth2_provider.backends.OAuth2Backend",
    "guardian.backends.ObjectPermissionBackend",
)

# django-axes — failed-login lockout. Set AXES_ENABLED=false to disable in tests.
AXES_ENABLED = os.getenv("AXES_ENABLED", "true").strip().lower() in ("1", "true", "yes", "on")
AXES_FAILURE_LIMIT = int(os.getenv("AXES_FAILURE_LIMIT", "5") or "5")
AXES_COOLOFF_TIME = timedelta(minutes=int(os.getenv("AXES_COOLOFF_MINUTES", "15") or "15"))
AXES_LOCKOUT_PARAMETERS = ["username", "ip_address"]
AXES_RESET_ON_SUCCESS = True
AXES_LOCKOUT_TEMPLATE = "auth/lockout.html"
AXES_VERBOSE = False

# Session idle timeout (minutes default = 60). SSE keepalive + badge polling
# are excluded by SessionIdleTimeoutMiddleware so a backgrounded tab does not
# hold the session open indefinitely.
SESSION_IDLE_TIMEOUT_SECONDS = int(os.getenv("SESSION_IDLE_TIMEOUT_SECONDS", "3600") or "3600")
SESSION_SAVE_EVERY_REQUEST = True

ANONYMOUS_USER_NAME = None

LOGIN_URL = "/accounts/login/"
LOGIN_REDIRECT_URL = "/dashboard/"
LOGOUT_REDIRECT_URL = "/accounts/login/"

DEFAULT_FROM_EMAIL = os.getenv("DEFAULT_FROM_EMAIL", "noreply@ruforum.org")
SERVER_EMAIL = DEFAULT_FROM_EMAIL

# Read SMTP config from env so every settings module (including base, used by
# the Celery worker container) picks up the same outbound mailer. Falls back
# to console output when no EMAIL_HOST is configured.
if os.getenv("EMAIL_HOST", "").strip():
    EMAIL_BACKEND = os.getenv("EMAIL_BACKEND", "django.core.mail.backends.smtp.EmailBackend")
    EMAIL_HOST = os.getenv("EMAIL_HOST", "localhost")
    EMAIL_PORT = int(os.getenv("EMAIL_PORT", "587"))
    EMAIL_USE_TLS = os.getenv("EMAIL_USE_TLS", "True").lower() == "true"
    EMAIL_USE_SSL = os.getenv("EMAIL_USE_SSL", "False").lower() == "true"
    EMAIL_HOST_USER = os.getenv("EMAIL_HOST_USER", "")
    EMAIL_HOST_PASSWORD = os.getenv("EMAIL_HOST_PASSWORD", "")
else:
    EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
MFA_TOTP_ISSUER = os.getenv("MFA_TOTP_ISSUER", "RUFORUM IILMP")

CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://127.0.0.1:6379/1")
# Default django-db; set CELERY_RESULT_BACKEND only if you intentionally use Redis RPC results.
CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "django-db")
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_TIMEZONE = TIME_ZONE
CELERY_BEAT_SCHEDULER = "django_celery_beat.schedulers:DatabaseScheduler"

# SME-Hub × M&EL — number of days of inactivity before a tracking record is
# flagged as stalled (FRSME-MEI012).
SMEHUB_STALL_THRESHOLD_DAYS = int(os.getenv("SMEHUB_STALL_THRESHOLD_DAYS", "90"))

REDIS_URL = os.getenv("REDIS_URL", "redis://127.0.0.1:6379/0")
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 = {
    "DEFAULT_AUTHENTICATION_CLASSES": (
        "rest_framework_simplejwt.authentication.JWTAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ),
    "DEFAULT_PERMISSION_CLASSES": ("rest_framework.permissions.IsAuthenticated",),
    "DEFAULT_PAGINATION_CLASS": "apps.core.pagination.paginators.StandardResultsPagination",
    # Kept for DRF compatibility; StandardResultsPagination.page_size is authoritative.
    "PAGE_SIZE": 75,
    "DEFAULT_FILTER_BACKENDS": (
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.SearchFilter",
        "rest_framework.filters.OrderingFilter",
    ),
    "DEFAULT_THROTTLE_CLASSES": (
        "rest_framework.throttling.AnonRateThrottle",
        "rest_framework.throttling.UserRateThrottle",
    ),
    # PRD Table 65: per-minute client budgets (standard tier ~100–300/min; anon capped lower).
    # See setup/API_RATE_LIMITS.md for reporting/bulk tiers and gateway-level burst/concurrency.
    "DEFAULT_THROTTLE_RATES": {
        "anon": "90/minute",
        "user": "200/minute",
        "jwt_obtain": "15/minute",
    },
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
    "EXCEPTION_HANDLER": "apps.core.exceptions.handlers.custom_exception_handler",
}

SIMPLE_JWT = {
    "ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
    "REFRESH_TOKEN_LIFETIME": timedelta(days=7),
    "ROTATE_REFRESH_TOKENS": True,
    "BLACKLIST_AFTER_ROTATION": True,
}

SPECTACULAR_SETTINGS = {
    "TITLE": "RUFORUM IILMP API",
    "DESCRIPTION": "Integrated Information, Learning, and Management Platform",
    "VERSION": "1.0.0",
    "SERVE_INCLUDE_SCHEMA": False,
}

CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOWED_ORIGINS = [
    o.strip()
    for o in os.getenv("CORS_ALLOWED_ORIGINS", "http://127.0.0.1:8000,http://localhost:8000").split(
        ","
    )
    if o.strip()
]

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
        },
    },
    "root": {
        "handlers": ["console"],
        "level": "INFO",
    },
}

CSRF_TRUSTED_ORIGINS = [
    o.strip()
    for o in os.getenv("CSRF_TRUSTED_ORIGINS", "http://127.0.0.1:8000,http://localhost:8000").split(
        ","
    )
    if o.strip()
]

# Optional ClamAV (clamd) over TCP — when CLAMD_TCP_HOST is set, all module uploads
# (Repository, MEL, RIMS, Alumni, SME-Hub, REP) run an INSTREAM scan after libmagic.
# Set CLAMD_ENABLED=false to disable scanning even when a host is configured (test/dev).
CLAMD_TCP_HOST = os.getenv("CLAMD_TCP_HOST", "").strip()
CLAMD_TCP_PORT = int(os.getenv("CLAMD_TCP_PORT", "3310") or "3310")
CLAMD_ENABLED = os.getenv("CLAMD_ENABLED", "true").strip().lower() in ("1", "true", "yes", "on")

# Notifications — Server-Sent Events tuning. The badge keeps an EventSource
# open and the server polls the DB every NOTIFICATION_SSE_INTERVAL_SECONDS.
# Each connection is capped at NOTIFICATION_SSE_MAX_SECONDS so workers free up;
# the browser EventSource auto-reconnects.
NOTIFICATION_SSE_INTERVAL_SECONDS = int(os.getenv("NOTIFICATION_SSE_INTERVAL_SECONDS", "5") or "5")
NOTIFICATION_SSE_MAX_SECONDS = int(os.getenv("NOTIFICATION_SSE_MAX_SECONDS", "300") or "300")

# REP forum / DM / wiki / group SSE streams. Same shape as the notification
# stream — clients open EventSource, server polls the DB every
# REP_STREAM_INTERVAL_SECONDS for new rows, emits an `update` event when found.
# Connections cap at REP_STREAM_MAX_SECONDS; the browser auto-reconnects.
REP_STREAM_INTERVAL_SECONDS = int(os.getenv("REP_STREAM_INTERVAL_SECONDS", "5") or "5")
REP_STREAM_MAX_SECONDS = int(os.getenv("REP_STREAM_MAX_SECONDS", "300") or "300")

# Course-scoped left-rail subnav (P1-2). Defaults on; set REP_COURSE_SUBNAV_ENABLED=0
# to kill switch the partial across all course-scoped surfaces.
REP_COURSE_SUBNAV_ENABLED = os.getenv("REP_COURSE_SUBNAV_ENABLED", "1") == "1"

# FRREP-DCI012 — inbound mail-to-Repository ingestion. When REPO_INBOUND_IMAP_HOST
# is empty, the Celery beat task `poll_repository_inbox` short-circuits, so leaving
# it blank in dev / staging is safe. Production sets the IMAP credentials and the
# poller fetches UNSEEN messages every 5 minutes per `config/celery.py`.
REPO_INBOUND_IMAP_HOST = os.getenv("REPO_INBOUND_IMAP_HOST", "").strip()
REPO_INBOUND_IMAP_PORT = int(os.getenv("REPO_INBOUND_IMAP_PORT", "993") or "993")
REPO_INBOUND_IMAP_USER = os.getenv("REPO_INBOUND_IMAP_USER", "").strip()
REPO_INBOUND_IMAP_PASSWORD = os.getenv("REPO_INBOUND_IMAP_PASSWORD", "")
REPO_INBOUND_IMAP_FOLDER = os.getenv("REPO_INBOUND_IMAP_FOLDER", "INBOX")
REPO_INBOUND_PROCESSED_FOLDER = os.getenv("REPO_INBOUND_PROCESSED_FOLDER", "Processed")
REPO_INBOUND_FAILED_FOLDER = os.getenv("REPO_INBOUND_FAILED_FOLDER", "Failed")
REPO_INBOUND_BATCH_LIMIT = int(os.getenv("REPO_INBOUND_BATCH_LIMIT", "50") or "50")
REPO_INBOUND_SERVICE_USER_EMAIL = os.getenv(
    "REPO_INBOUND_SERVICE_USER_EMAIL", "mail-ingest@iilmp.org"
)

# Repository / LangChain integration (Phase 2).
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY", "").strip()
LANGCHAIN_CHAT_MODEL_FAST = os.getenv("LANGCHAIN_CHAT_MODEL_FAST", "gpt-4o-mini")
LANGCHAIN_CHAT_MODEL_DEEP = os.getenv("LANGCHAIN_CHAT_MODEL_DEEP", "gpt-4o")
LANGCHAIN_EMBEDDING_MODEL = os.getenv("LANGCHAIN_EMBEDDING_MODEL", "text-embedding-3-small")
LANGCHAIN_EMBEDDING_DIMENSIONS = int(os.getenv("LANGCHAIN_EMBEDDING_DIMENSIONS", "1536"))
REPOSITORY_PGVECTOR_COLLECTION = os.getenv("REPOSITORY_PGVECTOR_COLLECTION", "repository_documents")
REPOSITORY_MEL_PUSH_URL = os.getenv("REPOSITORY_MEL_PUSH_URL", "").strip()
REPOSITORY_MEL_PUSH_TOKEN = os.getenv("REPOSITORY_MEL_PUSH_TOKEN", "").strip()

# MEL RAG thresholds (fallback when no per-logframe RagThreshold row exists).
# Percentage of target achieved: ≥ green_min → GREEN, ≥ amber_min → AMBER, else → RED.
MEL_RAG_DEFAULT = {
    "green": float(os.getenv("MEL_RAG_GREEN_MIN", "85")),
    "amber": float(os.getenv("MEL_RAG_AMBER_MIN", "60")),
}
# Grace period (in days past scheduled end) before a planned Activity is flagged as delayed.
MEL_ACTIVITY_DELAY_GRACE_DAYS = int(os.getenv("MEL_ACTIVITY_DELAY_GRACE_DAYS", "0"))

# Alumni / ORCID integration (Phase 5)
ORCID_API_BASE_URL = os.getenv("ORCID_API_BASE_URL", "https://pub.orcid.org").rstrip("/")
ORCID_API_TIMEOUT = int(os.getenv("ORCID_API_TIMEOUT", "15"))
ALUMNI_DIRECTORY_PAGE_SIZE = int(os.getenv("ALUMNI_DIRECTORY_PAGE_SIZE", "24"))
ALUMNI_MAX_CSV_ROWS = int(os.getenv("ALUMNI_MAX_CSV_ROWS", "10000"))
ALUMNI_ORCID_FAILURE_ALERT_THRESHOLD = int(os.getenv("ALUMNI_ORCID_FAILURE_ALERT_THRESHOLD", "3"))

# ── Moodle LMS integration ────────────────────────────────────────────────
# Real Moodle replaced the home-grown REP LMS. `MOODLE_HOST` is the browser-facing
# host (used for nav links + OAuth redirects); `MOODLE_BASE_URL` is the
# server-to-server URL the MEL pull uses with `MOODLE_WS_TOKEN`.
MOODLE_HOST = os.getenv("MOODLE_HOST", "localhost:8081")
MOODLE_BASE_URL = os.getenv("MOODLE_BASE_URL", "http://moodle:8080").rstrip("/")
MOODLE_WS_TOKEN = os.getenv("MOODLE_WS_TOKEN", "")
MOODLE_WS_TIMEOUT = int(os.getenv("MOODLE_WS_TIMEOUT", "20"))
# TLS verification for server-to-server calls to Moodle. True (a real cert) in
# prod; dev sets False because Moodle is fronted by Caddy's self-signed internal CA.
MOODLE_WS_VERIFY = os.getenv("MOODLE_WS_VERIFY", "True").lower() == "true"

# IILMP → Moodle role projection (SSO). Only roles meaningful at Moodle's SYSTEM
# context are mapped — course roles (student/teacher) come from enrolment, so
# learners/scholars correctly stay plain "authenticated users" until enrolled.
# IILMP is the source of truth; Moodle is a projection (synced at SSO login and
# via the `sync_moodle_roles` management command for backfill/demotions).
MOODLE_ROLE_SYNC_ENABLED = os.getenv("MOODLE_ROLE_SYNC_ENABLED", "True").lower() == "true"
MOODLE_ROLE_MAP = {
    "admin": "manager",
    "system_admin": "manager",
    "rep_learning_admin": "manager",
    "instructor": "coursecreator",
}
# Moodle role ids for the shortnames above. These are Moodle's install defaults;
# verify after any legacy DB restore: SELECT id, shortname FROM mdl_role;
MOODLE_ROLE_IDS = {
    "manager": int(os.getenv("MOODLE_ROLE_ID_MANAGER", "1")),
    "coursecreator": int(os.getenv("MOODLE_ROLE_ID_COURSECREATOR", "2")),
}
MOODLE_SYSTEM_CONTEXT_ID = int(os.getenv("MOODLE_SYSTEM_CONTEXT_ID", "1"))

# ── OAuth2 / OpenID Connect provider (SSO — "Log in with IILMP" in Moodle) ──
OAUTH2_PROVIDER = {
    "OIDC_ENABLED": True,
    # PEM-encoded RSA private key for signing OIDC ID tokens. Generate once with
    # `openssl genrsa 4096` and set OIDC_RSA_PRIVATE_KEY in the environment.
    "OIDC_RSA_PRIVATE_KEY": os.getenv("OIDC_RSA_PRIVATE_KEY", ""),
    "OAUTH2_VALIDATOR_CLASS": "apps.core.authentication.oidc.IILMPOAuth2Validator",
    "SCOPES": {
        "openid": "OpenID Connect scope",
        "profile": "Profile (name)",
        "email": "Email address",
    },
    # Moodle core auth_oauth2 does not send a PKCE code_challenge, so requiring it
    # rejects the authorization request. Keep it required by default (prod), but
    # allow turning it off per-environment (dev sets OAUTH2_PKCE_REQUIRED=False).
    "PKCE_REQUIRED": os.getenv("OAUTH2_PKCE_REQUIRED", "True").lower() == "true",
    "ALLOWED_REDIRECT_URI_SCHEMES": ["http", "https"] if DEBUG else ["https"],
}
