"""
Django settings for Mnemosyne project.

Environment-variable driven settings for dual-database (PostgreSQL + Neo4j).
Uses django-environ to load from .env file.
"""

import os
from pathlib import Path

import environ

# Apply SSL bypass before any HTTP-making imports when CASDOOR_SSL_VERIFY=false.
# This must run before allauth or requests make outbound calls.
if os.environ.get('CASDOOR_SSL_VERIFY', 'true').lower() == 'false':
    import themis.ssl_patch  # noqa: F401

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

# Initialize environment variables
env = environ.Env(
    DEBUG=(bool, True),
)

# Read .env file if it exists. Lives at the repo root (one level above the
# Django project package), shared with the docker-compose stack.
environ.Env.read_env(BASE_DIR.parent / ".env")

# --- Security ---
SECRET_KEY = env("SECRET_KEY", default="django-insecure-change-me-in-production")
DEBUG = env.bool("DEBUG", default=True)
ALLOWED_HOSTS = env.list("ALLOWED_HOSTS", default=["localhost", "127.0.0.1"])
CSRF_TRUSTED_ORIGINS = env.list(
    "CSRF_TRUSTED_ORIGINS", default=["http://localhost:8000"]
)

# Proxy settings — trust X-Forwarded-Proto from HAProxy
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
USE_X_FORWARDED_HOST = True
USE_X_FORWARDED_PORT = True

# --- SSO (Casdoor / Allauth) ---
CASDOOR_ENABLED = env.bool("CASDOOR_ENABLED", default=False)
CASDOOR_ORIGIN = env("CASDOOR_ORIGIN", default="")
CASDOOR_ORIGIN_FRONTEND = env("CASDOOR_ORIGIN_FRONTEND", default="")
CASDOOR_CLIENT_ID = env("CASDOOR_CLIENT_ID", default="")
CASDOOR_CLIENT_SECRET = env("CASDOOR_CLIENT_SECRET", default="")
CASDOOR_ORG_NAME = env("CASDOOR_ORG_NAME", default="")
ALLOW_LOCAL_LOGIN = env.bool("ALLOW_LOCAL_LOGIN", default=False)

# --- LLM API Encryption ---
LLM_API_SECRETS_ENCRYPTION_KEY = env(
    "LLM_API_SECRETS_ENCRYPTION_KEY", default=""
)

# --- Application definition ---
INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "django.contrib.humanize",
    # Third-party
    "rest_framework",
    "storages",
    "django_neomodel",
    "django_prometheus",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "allauth.socialaccount.providers.openid_connect",
    # Mnemosyne apps
    "themis",
    "library",
    "llm_manager",
    "mcp_server",
]

# --- MCP Server ---
MCP_REQUIRE_AUTH = env.bool("MCP_REQUIRE_AUTH", default=True)

# --- Authentication backends ---
AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",
    "allauth.account.auth_backends.AuthenticationBackend",
]

# --- Allauth ---
ACCOUNT_LOGIN_METHODS = {"email"}
ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]
ACCOUNT_EMAIL_VERIFICATION = "optional"
ACCOUNT_SESSION_REMEMBER = True
ACCOUNT_LOGIN_ON_PASSWORD_RESET = True
ACCOUNT_UNIQUE_EMAIL = True
SOCIALACCOUNT_AUTO_SIGNUP = True
SOCIALACCOUNT_EMAIL_VERIFICATION = "none"
SOCIALACCOUNT_QUERY_EMAIL = True
SOCIALACCOUNT_STORE_TOKENS = True
SOCIALACCOUNT_ADAPTER = "themis.adapters.CasdoorAccountAdapter"
ACCOUNT_ADAPTER = "themis.adapters.LocalAccountAdapter"
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
SESSION_COOKIE_AGE = 28800
SESSION_SAVE_EVERY_REQUEST = True

if CASDOOR_ENABLED:
    SOCIALACCOUNT_PROVIDERS = {
        "openid_connect": {
            "APPS": [
                {
                    "provider_id": "casdoor",
                    "name": "Casdoor SSO",
                    "client_id": CASDOOR_CLIENT_ID,
                    "secret": CASDOOR_CLIENT_SECRET,
                    "settings": {
                        "server_url": f"{CASDOOR_ORIGIN}/.well-known/openid-configuration",
                    },
                }
            ],
            "OAUTH_PKCE_ENABLED": True,
        }
    }

MIDDLEWARE = [
    "django_prometheus.middleware.PrometheusBeforeMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "allauth.account.middleware.AccountMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "themis.middleware.TimezoneMiddleware",
    "themis.middleware.ThemeMiddleware",
    "django_prometheus.middleware.PrometheusAfterMiddleware",
]

ROOT_URLCONF = "mnemosyne.urls"

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "mnemosyne" / "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",
                "themis.context_processors.themis_settings",
                "themis.context_processors.user_preferences",
                "themis.context_processors.notifications",
            ],
        },
    },
]

WSGI_APPLICATION = "mnemosyne.wsgi.application"

# --- Database: PostgreSQL (Django ORM) ---
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": env("APP_DB_NAME", default="mnemosyne"),
        "USER": env("APP_DB_USER", default="mnemosyne"),
        "PASSWORD": env("APP_DB_PASSWORD", default="password"),
        "HOST": env("DB_HOST", default="portia.incus"),
        "PORT": env("DB_PORT", default="5432"),
    }
}

# --- Neo4j Graph Database ---
NEOMODEL_NEO4J_BOLT_URL = env(
    "NEOMODEL_NEO4J_BOLT_URL",
    default="bolt://neo4j:password@ariel.incus:25554",
)

# --- Password validation ---
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 ---
LANGUAGE_CODE = env("LANGUAGE_CODE", default="en-us")
TIME_ZONE = env("TIME_ZONE", default="UTC")
USE_I18N = True
USE_TZ = True

# --- Static files ---
STATIC_URL = "static/"
STATIC_ROOT = Path(env("STATIC_ROOT", default=str(BASE_DIR / "staticfiles")))

MEDIA_ROOT = Path(env("MEDIA_ROOT", default=str(BASE_DIR / "media")))
MEDIA_URL = "/media/"

# --- Storage (S3 or local) ---
USE_LOCAL_STORAGE = env.bool("USE_LOCAL_STORAGE", default=True)

if USE_LOCAL_STORAGE:
    STORAGES = {
        "default": {
            "BACKEND": "django.core.files.storage.FileSystemStorage",
        },
        "staticfiles": {
            "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
        },
    }
else:
    STORAGES = {
        "default": {
            "BACKEND": "storages.backends.s3boto3.S3Boto3Storage",
            "OPTIONS": {
                "access_key": env("AWS_ACCESS_KEY_ID", default=""),
                "secret_key": env("AWS_SECRET_ACCESS_KEY", default=""),
                "bucket_name": env("AWS_STORAGE_BUCKET_NAME", default="mnemosyne-content"),
                "endpoint_url": env("AWS_S3_ENDPOINT_URL", default=""),
                "use_ssl": env.bool("AWS_S3_USE_SSL", default=False),
                "default_acl": "private",
                "region_name": env("AWS_S3_REGION_NAME", default="us-east-1"),
                "file_overwrite": False,
                "querystring_auth": True,
                "verify": env.bool("AWS_S3_VERIFY", default=True),
            },
        },
        "staticfiles": {
            "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage",
        },
    }

# --- Source S3 buckets (cross-bucket reads for ingest) ---
# Mnemosyne ingests files that upstream systems write to their own S3
# buckets. Each ingest request carries a `source` identifier; the Celery
# ingest task looks up that source's read credentials here, fetches the
# file, and copies it into AWS_STORAGE_BUCKET_NAME (Mnemosyne's own bucket)
# before processing. Add a new entry per upstream source.
#
# Backwards compatible: the "daedalus" entry is built from the existing
# DAEDALUS_S3_* env vars.
DAEDALUS_S3_ENDPOINT_URL = env("DAEDALUS_S3_ENDPOINT_URL", default="")
DAEDALUS_S3_ACCESS_KEY_ID = env("DAEDALUS_S3_ACCESS_KEY_ID", default="")
DAEDALUS_S3_SECRET_ACCESS_KEY = env("DAEDALUS_S3_SECRET_ACCESS_KEY", default="")
DAEDALUS_S3_BUCKET_NAME = env("DAEDALUS_S3_BUCKET_NAME", default="daedalus")
DAEDALUS_S3_REGION_NAME = env("DAEDALUS_S3_REGION_NAME", default="us-east-1")
DAEDALUS_S3_USE_SSL = env.bool("DAEDALUS_S3_USE_SSL", default=False)
DAEDALUS_S3_VERIFY = env.bool("DAEDALUS_S3_VERIFY", default=True)

# Spelunker writes scraped/extracted documents to its own bucket.
SPELUNKER_S3_ENDPOINT_URL = env("SPELUNKER_S3_ENDPOINT_URL", default="")
SPELUNKER_S3_ACCESS_KEY_ID = env("SPELUNKER_S3_ACCESS_KEY_ID", default="")
SPELUNKER_S3_SECRET_ACCESS_KEY = env("SPELUNKER_S3_SECRET_ACCESS_KEY", default="")
SPELUNKER_S3_BUCKET_NAME = env("SPELUNKER_S3_BUCKET_NAME", default="spelunker")
SPELUNKER_S3_REGION_NAME = env("SPELUNKER_S3_REGION_NAME", default="us-east-1")
SPELUNKER_S3_USE_SSL = env.bool("SPELUNKER_S3_USE_SSL", default=False)
SPELUNKER_S3_VERIFY = env.bool("SPELUNKER_S3_VERIFY", default=True)

# Kairos renders synced mail to text documents in its own bucket.
KAIROS_S3_ENDPOINT_URL = env("KAIROS_S3_ENDPOINT_URL", default="")
KAIROS_S3_ACCESS_KEY_ID = env("KAIROS_S3_ACCESS_KEY_ID", default="")
KAIROS_S3_SECRET_ACCESS_KEY = env("KAIROS_S3_SECRET_ACCESS_KEY", default="")
KAIROS_S3_BUCKET_NAME = env("KAIROS_S3_BUCKET_NAME", default="kairos")
KAIROS_S3_REGION_NAME = env("KAIROS_S3_REGION_NAME", default="us-east-1")
KAIROS_S3_USE_SSL = env.bool("KAIROS_S3_USE_SSL", default=False)
KAIROS_S3_VERIFY = env.bool("KAIROS_S3_VERIFY", default=True)

# Registry keyed by the ingest `source` field. Unknown/blank sources fall
# back to "daedalus" for backwards compatibility.
SOURCE_S3_BUCKETS = {
    "daedalus": {
        "endpoint_url": DAEDALUS_S3_ENDPOINT_URL,
        "access_key_id": DAEDALUS_S3_ACCESS_KEY_ID,
        "secret_access_key": DAEDALUS_S3_SECRET_ACCESS_KEY,
        "bucket_name": DAEDALUS_S3_BUCKET_NAME,
        "region_name": DAEDALUS_S3_REGION_NAME,
        "use_ssl": DAEDALUS_S3_USE_SSL,
        "verify": DAEDALUS_S3_VERIFY,
    },
    "spelunker": {
        "endpoint_url": SPELUNKER_S3_ENDPOINT_URL,
        "access_key_id": SPELUNKER_S3_ACCESS_KEY_ID,
        "secret_access_key": SPELUNKER_S3_SECRET_ACCESS_KEY,
        "bucket_name": SPELUNKER_S3_BUCKET_NAME,
        "region_name": SPELUNKER_S3_REGION_NAME,
        "use_ssl": SPELUNKER_S3_USE_SSL,
        "verify": SPELUNKER_S3_VERIFY,
    },
    "kairos-mail": {
        "endpoint_url": KAIROS_S3_ENDPOINT_URL,
        "access_key_id": KAIROS_S3_ACCESS_KEY_ID,
        "secret_access_key": KAIROS_S3_SECRET_ACCESS_KEY,
        "bucket_name": KAIROS_S3_BUCKET_NAME,
        "region_name": KAIROS_S3_REGION_NAME,
        "use_ssl": KAIROS_S3_USE_SSL,
        "verify": KAIROS_S3_VERIFY,
    },
}

# --- Celery / RabbitMQ ---
CELERY_BROKER_URL = env(
    "CELERY_BROKER_URL",
    default="amqp://mnemosyne:password@oberon.incus:5672/mnemosyne",
)
CELERY_RESULT_BACKEND = env("CELERY_RESULT_BACKEND", default="rpc://")
CELERY_ACCEPT_CONTENT = ["json"]
CELERY_TASK_SERIALIZER = "json"
CELERY_RESULT_SERIALIZER = "json"
CELERY_TIMEZONE = TIME_ZONE
CELERY_TASK_ALWAYS_EAGER = env.bool("CELERY_TASK_ALWAYS_EAGER", default=False)
CELERY_TASK_ACKS_LATE = True
CELERY_WORKER_PREFETCH_MULTIPLIER = 1
CELERY_TASK_ROUTES = {
    "library.tasks.embed_*": {"queue": "embedding"},
    "library.tasks.ingest_*": {"queue": "embedding"},
    "library.tasks.batch_*": {"queue": "batch"},
}

# --- Memcached ---
KVDB_LOCATION = env("KVDB_LOCATION", default="127.0.0.1:11211")
KVDB_PREFIX = env("KVDB_PREFIX", default="mnemosyne")

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": KVDB_LOCATION,
        "KEY_PREFIX": KVDB_PREFIX,
        "TIMEOUT": 300,
    }
}

# --- Email (smtp4dev on Oberon) ---
EMAIL_HOST = env("EMAIL_HOST", default="oberon.incus")
EMAIL_PORT = env.int("EMAIL_PORT", default=22025)
EMAIL_USE_TLS = env.bool("EMAIL_USE_TLS", default=False)
EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend"

# --- Django REST Framework ---
REST_FRAMEWORK = {
    "DEFAULT_AUTHENTICATION_CLASSES": [
        # Bearer first: unauthenticated requests get 401 + WWW-Authenticate: Bearer
        # (RFC-correct). SessionAuthentication still runs after; it picks up
        # browser session cookies when no Authorization header is present.
        "mcp_server.drf_auth.UserTokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
    ],
    "DEFAULT_PERMISSION_CLASSES": [
        "rest_framework.permissions.IsAuthenticated",
    ],
    "DEFAULT_PARSER_CLASSES": [
        "rest_framework.parsers.JSONParser",
        "rest_framework.parsers.FormParser",
        "rest_framework.parsers.MultiPartParser",
    ],
    "DEFAULT_RENDERER_CLASSES": [
        "rest_framework.renderers.JSONRenderer",
        "rest_framework.renderers.BrowsableAPIRenderer",
    ],
}

# --- Default primary key field type ---
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

# --- Login URLs ---
LOGIN_URL = "/accounts/login/"
LOGIN_REDIRECT_URL = "/dashboard/"
ACCOUNT_LOGOUT_REDIRECT_URL = "/"

# --- Embedding Pipeline (Phase 2) ---
EMBEDDING_BATCH_SIZE = env.int("EMBEDDING_BATCH_SIZE", default=8)
EMBEDDING_TIMEOUT = env.int("EMBEDDING_TIMEOUT", default=120)

# --- Search & Re-ranking (Phase 3) ---
SEARCH_VECTOR_TOP_K = env.int("SEARCH_VECTOR_TOP_K", default=50)
SEARCH_FULLTEXT_TOP_K = env.int("SEARCH_FULLTEXT_TOP_K", default=30)
SEARCH_GRAPH_MAX_DEPTH = env.int("SEARCH_GRAPH_MAX_DEPTH", default=2)
SEARCH_RRF_K = env.int("SEARCH_RRF_K", default=60)
SEARCH_DEFAULT_LIMIT = env.int("SEARCH_DEFAULT_LIMIT", default=20)
RERANKER_MAX_CANDIDATES = env.int("RERANKER_MAX_CANDIDATES", default=32)
RERANKER_TIMEOUT = env.int("RERANKER_TIMEOUT", default=30)

# --- Themis app settings ---
THEMIS_APP_NAME = "Mnemosyne"
THEMIS_NOTIFICATION_POLL_INTERVAL = 60
THEMIS_NOTIFICATION_MAX_AGE_DAYS = 90

# --- Structured Logging ---
# All log output is line-delimited JSON on stdout, one record per line.
# Alloy (running on the host / container sidecar) tails the container's
# stdout stream and ships to Loki.  No log files, no syslog — a single,
# uniform transport across every service on this host.
#
# Labels attached by Alloy (NOT embedded here): service, component,
# environment, hostname.  "component" is injected by the formatter as
# a static field based on the MNEMOSYNE_COMPONENT env var set per
# docker-compose service (app | mcp | worker).  This keeps the label
# shape consistent with Pallas and future services.
#
# Level policy (Ouranos Lab standard):
#   ERROR   — broken; requires human attention
#   WARNING — degraded but self-recovering; retries, skipped items
#   INFO    — lifecycle events and failures; no 200 OK health probes
#   DEBUG   — health-probe success, per-request detail, verbose traces
LOGGING_LEVEL = env("LOGGING_LEVEL", default="INFO")
CELERY_LOGGING_LEVEL = env("CELERY_LOGGING_LEVEL", default="INFO")
DJANGO_LOGGING_LEVEL = env("DJANGO_LOGGING_LEVEL", default="WARNING")
MNEMOSYNE_COMPONENT = env("MNEMOSYNE_COMPONENT", default="app")

LOGGING = {
    "version": 1,
    "disable_existing_loggers": False,
    "formatters": {
        # JSON formatter — one line of JSON per record.  Alloy's ``| json``
        # pipeline in LogQL will parse these fields into queryable columns
        # (level, logger, funcName, lineno, message, plus anything passed
        # via ``logger.info("...", extra={...})``).
        "json": {
            "()": "pythonjsonlogger.json.JsonFormatter",
            "fmt": (
                "%(asctime)s %(levelname)s %(name)s "
                "%(funcName)s %(lineno)d %(message)s"
            ),
            "rename_fields": {
                "asctime": "time",
                "levelname": "level",
                "name": "logger",
            },
            "static_fields": {
                "service": "mnemosyne",
                "component": MNEMOSYNE_COMPONENT,
            },
        },
    },
    "filters": {
        # Demotes successful health-probe access log lines from INFO to
        # DEBUG so production INFO output stays signal-only.  Applied to
        # django.server, gunicorn.access, and uvicorn.access.
        "suppress_health_access": {
            "()": "mnemosyne.log_filters.SuppressHealthAccessFilter",
        },
    },
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",
            "formatter": "json",
            "stream": "ext://sys.stdout",
        },
        # Separate handler for django/gunicorn access logs so we can apply
        # the health-path filter without affecting application loggers.
        "access": {
            "class": "logging.StreamHandler",
            "formatter": "json",
            "stream": "ext://sys.stdout",
            "filters": ["suppress_health_access"],
        },
    },
    "loggers": {
        "library": {
            "handlers": ["console"],
            "level": LOGGING_LEVEL,
            "propagate": False,
        },
        "llm_manager": {
            "handlers": ["console"],
            "level": LOGGING_LEVEL,
            "propagate": False,
        },
        "themis": {
            "handlers": ["console"],
            "level": LOGGING_LEVEL,
            "propagate": False,
        },
        "mcp_server": {
            "handlers": ["console"],
            "level": LOGGING_LEVEL,
            "propagate": False,
        },
        "celery": {
            "handlers": ["console"],
            "level": CELERY_LOGGING_LEVEL,
            "propagate": False,
        },
        "django": {
            "handlers": ["console"],
            "level": DJANGO_LOGGING_LEVEL,
            "propagate": False,
        },
        "django.request": {
            "handlers": ["console"],
            "level": DJANGO_LOGGING_LEVEL,
            "propagate": False,
        },
        # Django's runserver / gunicorn access logs — demote health probes
        # to DEBUG so "5xx on /ready/" is easy to spot in INFO.
        "django.server": {
            "handlers": ["access"],
            "level": DJANGO_LOGGING_LEVEL,
            "propagate": False,
        },
        "gunicorn.access": {
            "handlers": ["access"],
            "level": DJANGO_LOGGING_LEVEL,
            "propagate": False,
        },
        "uvicorn.access": {
            "handlers": ["access"],
            "level": LOGGING_LEVEL,
            "propagate": False,
        },
        # Noisy library internals — pin to WARNING regardless of root level
        # so we don't drown in HTTP-client debug spam when LOGGING_LEVEL=DEBUG.
        "httpx": {"handlers": ["console"], "level": "WARNING", "propagate": False},
        "httpcore": {"handlers": ["console"], "level": "WARNING", "propagate": False},
        "openai": {"handlers": ["console"], "level": "WARNING", "propagate": False},
        "urllib3": {"handlers": ["console"], "level": "WARNING", "propagate": False},
        "botocore": {"handlers": ["console"], "level": "WARNING", "propagate": False},
        "boto3": {"handlers": ["console"], "level": "WARNING", "propagate": False},
        "s3transfer": {"handlers": ["console"], "level": "WARNING", "propagate": False},
        "neo4j": {"handlers": ["console"], "level": "WARNING", "propagate": False},
    },
    "root": {
        "handlers": ["console"],
        "level": LOGGING_LEVEL,
    },
}
