"""Django settings for the Hartling owner-portal.

One codebase, three isolated instances (one per property). Everything that
differs per property is configuration (environment), never code: site name,
branding, domain, database, and document store. See
../.logs/planning/rebuild-plan.md.
"""
import os
import sys
from pathlib import Path

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

# SECRET_KEY: real value comes from the environment in every deployed instance.
# The fallback is an obvious non-secret for local dev only (coding.md: no real
# secret in code). Production MUST set DJANGO_SECRET_KEY.
SECRET_KEY = os.environ.get(
    "DJANGO_SECRET_KEY",
    "dev-insecure-not-a-secret-set-DJANGO_SECRET_KEY-in-every-real-instance",
)
DEBUG = os.environ.get("DJANGO_DEBUG", "0") == "1"
ALLOWED_HOSTS = [
    h for h in os.environ.get("DJANGO_ALLOWED_HOSTS", "localhost,127.0.0.1").split(",") if h
]
CSRF_TRUSTED_ORIGINS = [
    o
    for o in os.environ.get(
        "DJANGO_CSRF_TRUSTED_ORIGINS", "https://hartlingowners.demoing.info"
    ).split(",")
    if o
]

# Per-instance branding — the only real "tenancy". Differs per property.
SITE_NAME = os.environ.get("PORTAL_SITE_NAME", "Owner Portal")
# Branding is per-instance config, never a code fork: the three portals share
# every template and stylesheet and differ only in these values.
BRAND_ACCENT = os.environ.get("PORTAL_ACCENT", "#0f5f6b")
BRAND_ACCENT_DARK = os.environ.get("PORTAL_ACCENT_DARK", "#0b474f")
# The masthead is a dark field because all three resort logos are white-only —
# there is no dark variant of any of them, so a light header has nothing to
# show. Each property sets its own depth of dark and its own paper tint.
BRAND_HEADER = os.environ.get("PORTAL_HEADER", "#1f2a2c")
BRAND_GROUND = os.environ.get("PORTAL_GROUND", "#f7f6f3")
BRAND_LOGO = os.environ.get("PORTAL_LOGO", "")
# The three marks are drawn to different proportions — one is a wide script,
# one stacks a medallion over a wordmark. A single height makes the stacked one
# read at half the size of the others, so the height is set per property.
BRAND_LOGO_HEIGHT = os.environ.get("PORTAL_LOGO_HEIGHT", "40")
# A photograph of the property, sat far behind the browse panel. Owners like
# seeing their own resort; it is held to a whisper so it never competes with
# the documents. Optional — without one the panel is simply the flat colour.
BRAND_RAIL_IMAGE = os.environ.get("PORTAL_RAIL_IMAGE", "")
# The property's own typefaces, lifted from its resort site. Per-instance
# because the three resorts are three brands, not one.
BRAND_FONT_CSS = os.environ.get(
    "PORTAL_FONT_CSS",
    "https://fonts.googleapis.com/css2?family=Newsreader:opsz,wght@6..72,400;6..72,500;"
    "6..72,600&family=Public+Sans:wght@400;500;600;700&display=swap")
BRAND_FONT_SERIF = os.environ.get("PORTAL_FONT_SERIF", '"Newsreader", Georgia, serif')
BRAND_FONT_SANS = os.environ.get(
    "PORTAL_FONT_SANS", '"Public Sans", system-ui, -apple-system, sans-serif')
# The resort's full-strength colour. It is too light to sit behind white text,
# but on the dark masthead it is legible and unmistakably theirs — which is the
# one place the undiluted brand colour belongs.
BRAND_VIVID = os.environ.get("PORTAL_VIVID", "")
# A tile width turns the panel image into a repeating texture (the resorts use
# small tileable patterns); leaving it empty treats the image as one photograph
# covering the panel.
BRAND_RAIL_TILE = os.environ.get("PORTAL_RAIL_TILE", "")
# The sign-in scene: a slideshow of the property's own photography. The
# prefix names files static/login/<prefix>-1.jpg … -<count>.jpg; empty means
# no slideshow and the plain ground shows instead.
BRAND_LOGIN_PREFIX = os.environ.get("PORTAL_LOGIN_PREFIX", "")
BRAND_LOGIN_COUNT = int(os.environ.get("PORTAL_LOGIN_COUNT", "4"))

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",
    "accounts",
    "documents",
    "backoffice",
]

MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "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",
]

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

TEMPLATES = [
    {
        "BACKEND": "django.template.backends.django.DjangoTemplates",
        "DIRS": [BASE_DIR / "templates"],
        "APP_DIRS": True,
        "OPTIONS": {
            "context_processors": [
                "django.template.context_processors.request",
                "django.contrib.auth.context_processors.auth",
                "django.contrib.messages.context_processors.messages",
                "config.context_processors.branding",
                "config.context_processors.document_dates",
            ],
        },
    },
]

# Postgres from day one for dev and prod. SQLite is used ONLY as the unit-test
# substrate (the hasher tests are DB-independent) so tests need no running
# server; the application itself never runs on SQLite.
if "test" in sys.argv:
    DATABASES = {"default": {"ENGINE": "django.db.backends.sqlite3", "NAME": ":memory:"}}
else:
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.postgresql",
            "NAME": os.environ.get("POSTGRES_DB", "hartling_portal"),
            "USER": os.environ.get("POSTGRES_USER", "portal"),
            "PASSWORD": os.environ.get("POSTGRES_PASSWORD", ""),
            "HOST": os.environ.get("POSTGRES_HOST", "127.0.0.1"),
            "PORT": os.environ.get("POSTGRES_PORT", "5432"),
        }
    }

AUTH_USER_MODEL = "accounts.User"

# Owners know their email address; the WordPress username they were migrated
# with is often something they never chose. Accept either.
AUTHENTICATION_BACKENDS = ["accounts.backends.UsernameOrEmailBackend"]

# Argon2 is the preferred (default) hasher — new and rehashed passwords use it.
# The two WordPress hashers are verify-only and sit LAST, so a legacy hash still
# authenticates a migrated owner and Django transparently upgrades it to Argon2
# on the next successful login (just-in-time migration).
PASSWORD_HASHERS = [
    "django.contrib.auth.hashers.Argon2PasswordHasher",
    "django.contrib.auth.hashers.BCryptSHA256PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2PasswordHasher",
    "django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher",
    "accounts.hashers.WordPressBcryptHasher",
    "accounts.hashers.WordPressPhpassHasher",
]

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

STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "staticfiles"
STATICFILES_DIRS = [BASE_DIR / "static"]
STORAGES = {
    "default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
    "staticfiles": {
        # The manifest backend requires a collectstatic-built manifest, which a
        # test run has no reason to produce — use plain storage under test.
        "BACKEND": (
            "django.contrib.staticfiles.storage.StaticFilesStorage"
            if "test" in sys.argv
            else "whitenoise.storage.CompressedManifestStaticFilesStorage"
        )
    },
}
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

# Gated document storage — OUTSIDE the web root, served only through the
# authenticated download view (documents.views). Never a public URL. Runtime
# state lives at the workspace root per the v8 shape; override per instance.
# Document dates are WordPress POST dates — when the file was put on the old
# site, not when the document itself is from. That is why the 2023 AGM minutes
# carry a 2026 date. The client asked for them to be hidden rather than shown
# wrongly, so nothing date-derived is rendered: not the row date, not the
# detail date, and not the year filters built on the same value. The dates
# stay in the database and in the staff edit form; flip this to True once
# documents carry real dates of their own.
SHOW_DOCUMENT_DATES = os.environ.get("PORTAL_SHOW_DOCUMENT_DATES", "0") == "1"

PRIVATE_MEDIA_ROOT = os.environ.get(
    "PRIVATE_MEDIA_ROOT", str(BASE_DIR.parent / "data" / "documents")
)
DOWNLOAD_RATE_LIMIT_PER_MIN = int(os.environ.get("DOWNLOAD_RATE_LIMIT_PER_MIN", "60"))

LOGIN_URL = "login"
LOGIN_REDIRECT_URL = "/"
LOGOUT_REDIRECT_URL = "/"

# Email for password resets and invitations. Defaults to the console backend so
# development never silently depends on a mail server; production sets
# EMAIL_HOST and friends and the SMTP backend is used automatically.
EMAIL_HOST = os.environ.get("EMAIL_HOST", "")
EMAIL_PORT = int(os.environ.get("EMAIL_PORT", "587"))
EMAIL_HOST_USER = os.environ.get("EMAIL_HOST_USER", "")
EMAIL_HOST_PASSWORD = os.environ.get("EMAIL_HOST_PASSWORD", "")
EMAIL_USE_TLS = os.environ.get("EMAIL_USE_TLS", "1") == "1"
DEFAULT_FROM_EMAIL = os.environ.get("DEFAULT_FROM_EMAIL", "no-reply@example.com")
EMAIL_BACKEND = (
    "django.core.mail.backends.smtp.EmailBackend"
    if EMAIL_HOST
    else "django.core.mail.backends.console.EmailBackend"
)
PASSWORD_RESET_TIMEOUT = 60 * 60 * 6  # 6 hours

# Security headers/cookies. Behind TLS in every deployed instance; the flags are
# env-gated so local http dev still works.
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# Tell browsers never to try this site over plain HTTP again. Caddy already
# redirects, but a redirect still lets the first request leave the machine in
# clear text — on a portal whose whole purpose is private documents that is
# worth closing. `includeSubDomains` scopes to this host's own subdomains, not
# its siblings. No `preload`: that is a public-list submission covering the
# whole parent domain and is not ours to make.
SECURE_HSTS_SECONDS = 31536000                    # one year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = False
SECURE_REFERRER_POLICY = "same-origin"
# X_FRAME_OPTIONS is set below, deliberately SAMEORIGIN — the detail page
# previews PDFs in an iframe from this same origin, so DENY would break it.
SESSION_COOKIE_SECURE = os.environ.get("DJANGO_SECURE_COOKIES", "1") == "1"
CSRF_COOKIE_SECURE = os.environ.get("DJANGO_SECURE_COOKIES", "1") == "1"
SESSION_COOKIE_HTTPONLY = True
# SAMEORIGIN, not DENY: the document detail page previews PDFs in an iframe
# served from this same origin. Cross-origin framing is still blocked.
X_FRAME_OPTIONS = "SAMEORIGIN"
