"""
Django settings for WYGIWYH project.

Generated by 'django-admin startproject' using Django 5.1.1.

For more information on this file, see
https://docs.djangoproject.com/en/5.1/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.1/ref/settings/
"""

import os
import re
import sys
from pathlib import Path

from django.utils.text import slugify

SITE_TITLE = "WYGIWYH"
TITLE_SEPARATOR = "::"

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

# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.getenv("SECRET_KEY", "")

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = os.getenv("DEBUG", "false").lower() == "true"

ALLOWED_HOSTS = os.getenv("DJANGO_ALLOWED_HOSTS", "localhost 127.0.0.1").split(" ")
CSRF_TRUSTED_ORIGINS = os.getenv("URL", "http://localhost http://127.0.0.1").split(" ")

# Application definition

INSTALLED_APPS = [
    "django.contrib.admin",
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.sites",
    "whitenoise.runserver_nostatic",
    "django.contrib.staticfiles",
    "django_vite",
    "django.contrib.humanize",
    "django.contrib.postgres",
    "django_browser_reload",
    "django.forms",
    "debug_toolbar",
    "crispy_forms",
    "crispy_bootstrap5",
    "hijack",
    "hijack.contrib.admin",
    "django_filters",
    "import_export",
    "apps.users.apps.UsersConfig",
    "procrastinate.contrib.django",
    "apps.transactions.apps.TransactionsConfig",
    "apps.currencies.apps.CurrenciesConfig",
    "apps.accounts.apps.AccountsConfig",
    "apps.net_worth.apps.NetWorthConfig",
    "apps.import_app.apps.ImportConfig",
    "apps.export_app.apps.ExportConfig",
    "apps.api.apps.ApiConfig",
    "cachalot",
    "rest_framework",
    "rest_framework.authtoken",
    "drf_spectacular",
    "oauth2_provider",
    "django_cotton",
    "apps.rules.apps.RulesConfig",
    "apps.calendar_view.apps.CalendarViewConfig",
    "apps.dca.apps.DcaConfig",
    "pwa",
    "allauth",
    "allauth.account",
    "allauth.socialaccount",
    "allauth.socialaccount.providers.openid_connect",
    "apps.common.apps.CommonConfig",
]

SITE_ID = 1

MIDDLEWARE = [
    "django_browser_reload.middleware.BrowserReloadMiddleware",
    "apps.common.middleware.thread_local.ThreadLocalMiddleware",
    "debug_toolbar.middleware.DebugToolbarMiddleware",
    "django.middleware.security.SecurityMiddleware",
    "whitenoise.middleware.WhiteNoiseMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.locale.LocaleMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "apps.common.middleware.localization.LocalizationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "hijack.middleware.HijackUserMiddleware",
    "allauth.account.middleware.AccountMiddleware",
]

ROOT_URLCONF = "WYGIWYH.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",
            ],
        },
    },
]

STORAGES = {
    "staticfiles": {
        "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
    },
}

WHITENOISE_MANIFEST_STRICT = False


def immutable_file_test(path, url):
    # Match vite (rollup)-generated hashes, à la, `some_file-CSliV9zW.js`
    return re.match(r"^.+[.-][0-9a-zA-Z_-]{8,12}\..+$", url)


WHITENOISE_IMMUTABLE_FILE_TEST = immutable_file_test

WSGI_APPLICATION = "WYGIWYH.wsgi.application"


# Database
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases

THREADS = int(os.getenv("GUNICORN_THREADS", 1))
MAX_POOL_SIZE = THREADS + 1

DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": os.getenv("SQL_DATABASE"),
        "USER": os.getenv("SQL_USER", "user"),
        "PASSWORD": os.getenv("SQL_PASSWORD", "password"),
        "HOST": os.getenv("SQL_HOST", "localhost"),
        "PORT": os.getenv("SQL_PORT", "5432"),
        "CONN_MAX_AGE": 0,
        "CONN_HEALTH_CHECKS": True,
        "OPTIONS": {
            "pool": {
                "min_size": 1,
                "max_size": MAX_POOL_SIZE,
                "timeout": 10,
                "max_lifetime": 600,
                "max_idle": 300,
            },
        },
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators

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

AUTH_USER_MODEL = "users.User"


# Internationalization
# https://docs.djangoproject.com/en/5.1/topics/i18n/

LANGUAGE_CODE = "en"
LANGUAGES = (
    ("af", "Afrikaans"),
    ("ar", "العربية"),
    ("ar-dz", "العربية (الجزائر)"),  # Algerian Arabic often uses the base name + region
    ("ast", "Asturianu"),
    ("az", "Azərbaycan"),
    ("bg", "Български"),
    ("be", "Беларуская"),
    ("bn", "বাংলা"),
    ("br", "Brezhoneg"),
    ("bs", "Bosanski"),
    ("ca", "Català"),
    ("ckb", "کوردیی ناوەندی"),  # Central Kurdish (Sorani)
    ("cs", "Čeština"),
    ("cy", "Cymraeg"),
    ("da", "Dansk"),
    ("de", "Deutsch"),
    ("dsb", "Dolnoserbšćina"),
    ("el", "Ελληνικά"),
    ("en", "English"),
    ("en-au", "English (Australia)"),
    ("en-gb", "English (UK)"),
    ("eo", "Esperanto"),
    ("es", "Español"),
    ("es-ar", "Español (Argentina)"),
    ("es-co", "Español (Colombia)"),
    ("es-mx", "Español (México)"),
    ("es-ni", "Español (Nicaragua)"),
    ("es-ve", "Español (Venezuela)"),
    ("et", "Eesti"),
    ("eu", "Euskara"),
    ("fa", "فارسی"),
    ("fi", "Suomi"),
    ("fr", "Français"),
    ("fy", "Frysk"),
    ("ga", "Gaeilge"),
    ("gd", "Gàidhlig"),
    ("gl", "Galego"),
    ("he", "עברית"),
    ("hi", "हिन्दी"),
    ("hr", "Hrvatski"),
    ("hsb", "Hornjoserbšćina"),
    ("hu", "Magyar"),
    ("hy", "Հայերեն"),
    ("ia", "Interlingua"),
    ("id", "Bahasa Indonesia"),
    ("ig", "Igbo"),
    ("io", "Ido"),
    ("is", "Íslenska"),
    ("it", "Italiano"),
    ("ja", "日本語"),
    ("ka", "ქართული"),
    ("kab", "Taqbaylit"),
    ("kk", "Қазақша"),
    ("km", "ខ្មែរ"),
    ("kn", "ಕನ್ನಡ"),
    ("ko", "한국어"),
    ("ky", "Кыргызча"),
    ("lb", "Lëtzebuergesch"),
    ("lt", "Lietuvių"),
    ("lv", "Latviešu"),
    ("mk", "Македонски"),
    ("ml", "മലയാളം"),
    ("mn", "Монгол"),
    ("mr", "मराठी"),
    ("ms", "Bahasa Melayu"),
    ("my", "မြန်မာဘာသာ"),
    ("nb", "Norsk (Bokmål)"),
    ("ne", "नेपाली"),
    ("nl", "Nederlands"),
    ("nn", "Norsk (Nynorsk)"),
    ("os", "Ирон"),  # Ossetic
    ("pa", "ਪੰਜਾਬੀ"),
    ("pl", "Polski"),
    ("pt", "Português"),
    ("pt-br", "Português (Brasil)"),
    ("ro", "Română"),
    ("ru", "Русский"),
    ("sk", "Slovenčina"),
    ("sl", "Slovenščina"),
    ("sq", "Shqip"),
    ("sr", "Српски"),
    ("sr-latn", "Srpski (Latinica)"),
    ("sv", "Svenska"),
    ("sw", "Kiswahili"),
    ("ta", "தமிழ்"),
    ("te", "తెలుగు"),
    ("tg", "Тоҷикӣ"),
    ("th", "ไทย"),
    ("tk", "Türkmençe"),
    ("tr", "Türkçe"),
    ("tt", "Татарча"),
    ("udm", "Удмурт"),
    ("ug", "ئۇيغۇرچە"),
    ("uk", "Українська"),
    ("ur", "اردو"),
    ("uz", "Oʻzbekcha"),
    ("vi", "Tiếng Việt"),
    ("zh-hans", "简体中文"),
    ("zh-hant", "繁體中文"),
)

TIME_ZONE = os.getenv("TZ", "UTC")

USE_I18N = True

USE_TZ = True

LOCALE_PATHS = [BASE_DIR / "locale"]


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.1/howto/static-files/

STATIC_URL = "static/"
STATIC_ROOT = BASE_DIR / "static_files"
ATTACHMENT_MEDIA_ROOT = BASE_DIR / "attachments"

STATICFILES_DIRS = [
    ROOT_DIR / "frontend" / "build",
    BASE_DIR / "static",
]

STATICFILES_FINDERS = [
    "django.contrib.staticfiles.finders.FileSystemFinder",
    "django.contrib.staticfiles.finders.AppDirectoriesFinder",
]

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.filebased.FileBasedCache",
        "LOCATION": "/var/tmp/django_cache",
    }
}

DJANGO_VITE_ASSETS_PATH = STATIC_ROOT
DJANGO_VITE_MANIFEST_PATH = DJANGO_VITE_ASSETS_PATH / "manifest.json"
DJANGO_VITE_DEV_MODE = os.getenv("DJANGO_VITE_DEV_MODE", "false").lower() == "true"
DJANGO_VITE_DEV_SERVER_PORT = int(os.getenv("DJANGO_VITE_DEV_SERVER_PORT", "5173"))
DJANGO_VITE_DEV_SERVER_HOST = os.getenv("DJANGO_VITE_DEV_SERVER_HOST", "localhost")

# Default primary key field type
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"

LOGIN_REDIRECT_URL = "/"
LOGIN_URL = "/login/"
LOGOUT_REDIRECT_URL = "/login/"
# Public base URL advertised in OAuth metadata. Falls back to the first entry
# of the existing space-separated URL env var, then to the request host.
PUBLIC_BASE_URL = (
    os.getenv("PUBLIC_BASE_URL", "") or os.getenv("URL", "").split(" ")[0]
).rstrip("/")

# Allauth settings
AUTHENTICATION_BACKENDS = [
    "django.contrib.auth.backends.ModelBackend",  # Keep default
    "allauth.account.auth_backends.AuthenticationBackend",
]

SOCIALACCOUNT_PROVIDERS = {"openid_connect": {"APPS": []}}

if (
    os.getenv("OIDC_CLIENT_ID")
    and os.getenv("OIDC_CLIENT_SECRET")
    and os.getenv("OIDC_SERVER_URL")
):
    SOCIALACCOUNT_PROVIDERS["openid_connect"]["APPS"].append(
        {
            "provider_id": slugify(os.getenv("OIDC_CLIENT_NAME", "OpenID Connect")),
            "name": os.getenv("OIDC_CLIENT_NAME", "OpenID Connect"),
            "client_id": os.getenv("OIDC_CLIENT_ID"),
            "secret": os.getenv("OIDC_CLIENT_SECRET"),
            "settings": {
                "server_url": os.getenv("OIDC_SERVER_URL"),
            },
        }
    )

ACCOUNT_LOGIN_METHODS = {"email"}
ACCOUNT_SIGNUP_FIELDS = ["email*", "password1*", "password2*"]
ACCOUNT_USER_MODEL_USERNAME_FIELD = None
ACCOUNT_EMAIL_VERIFICATION = "none"
SOCIALACCOUNT_LOGIN_ON_GET = True
SOCIALACCOUNT_ONLY = True
SOCIALACCOUNT_AUTO_SIGNUP = os.getenv("OIDC_ALLOW_SIGNUP", "true").lower() == "true"
SOCIALACCOUNT_EMAIL_AUTHENTICATION = True
SOCIALACCOUNT_EMAIL_AUTHENTICATION_AUTO_CONNECT = True
ACCOUNT_ADAPTER = "allauth.account.adapter.DefaultAccountAdapter"
SOCIALACCOUNT_ADAPTER = "apps.users.adapters.AutoConnectSocialAccountAdapter"

# Personal access tokens. last_used_at is only rewritten once per interval to
# avoid a database write on every authenticated request.
API_TOKEN_LAST_USED_UPDATE_INTERVAL = int(
    os.getenv("API_TOKEN_LAST_USED_UPDATE_INTERVAL", "600")
)

# CRISPY FORMS
CRISPY_ALLOWED_TEMPLATE_PACKS = [
    "crispy_forms/pure_text",
    "crispy-daisyui",
]
CRISPY_TEMPLATE_PACK = "crispy-daisyui"

SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_COOKIE_AGE = int(os.getenv("SESSION_EXPIRY_TIME", 2678400))  # 31 days
SESSION_COOKIE_SECURE = os.getenv("HTTPS_ENABLED", "false").lower() == "true"

HTTPS_ENABLED = os.getenv("HTTPS_ENABLED", "false").lower() == "true"
ACCOUNT_DEFAULT_HTTP_PROTOCOL = "https" if HTTPS_ENABLED else "http"
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") if HTTPS_ENABLED else None

DEBUG_TOOLBAR_CONFIG = {
    "ROOT_TAG_EXTRA_ATTRS": "hx-preserve",
    # "SHOW_TOOLBAR_CALLBACK": lambda r: False,  # disables it
}
DEBUG_TOOLBAR_PANELS = [
    "debug_toolbar.panels.history.HistoryPanel",
    "debug_toolbar.panels.versions.VersionsPanel",
    "debug_toolbar.panels.timer.TimerPanel",
    "debug_toolbar.panels.settings.SettingsPanel",
    "debug_toolbar.panels.headers.HeadersPanel",
    "debug_toolbar.panels.request.RequestPanel",
    "debug_toolbar.panels.sql.SQLPanel",
    "debug_toolbar.panels.staticfiles.StaticFilesPanel",
    "debug_toolbar.panels.templates.TemplatesPanel",
    "debug_toolbar.panels.cache.CachePanel",
    "debug_toolbar.panels.signals.SignalsPanel",
    "debug_toolbar.panels.redirects.RedirectsPanel",
    "debug_toolbar.panels.profiling.ProfilingPanel",
    # "cachalot.panels.CachalotPanel",
]
INTERNAL_IPS = [
    "127.0.0.1",
]

if DEBUG:
    import socket

    hostname, _, ips = socket.gethostbyname_ex(socket.gethostname())
    INTERNAL_IPS += [".".join(ip.split(".")[:-1] + ["1"]) for ip in ips]
    try:
        _, _, ips = socket.gethostbyname_ex("node")
        INTERNAL_IPS.extend(ips)
    except socket.gaierror:
        # The node container isn't started (yet?)
        pass


REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    "DEFAULT_PERMISSION_CLASSES": [
        "apps.api.permissions.NotInDemoMode",
        "rest_framework.permissions.DjangoModelPermissions",
    ],
    "DEFAULT_FILTER_BACKENDS": [
        "django_filters.rest_framework.DjangoFilterBackend",
        "rest_framework.filters.OrderingFilter",
    ],
    "DEFAULT_AUTHENTICATION_CLASSES": [
        "oauth2_provider.contrib.rest_framework.OAuth2Authentication",
        "apps.api.authentication.APITokenAuthentication",
        "rest_framework.authentication.TokenAuthentication",
        "rest_framework.authentication.SessionAuthentication",
        "rest_framework.authentication.BasicAuthentication",
    ],
    "DEFAULT_PAGINATION_CLASS": "apps.api.custom.pagination.CustomPageNumberPagination",
    "DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}

OAUTH2_PROVIDER = {
    "PKCE_REQUIRED": True,
    "ACCESS_TOKEN_EXPIRE_SECONDS": int(
        os.getenv("OAUTH2_ACCESS_TOKEN_EXPIRE_SECONDS", "3600")
    ),
    "SCOPES": {
        "mcp": "Access WYGIWYH from MCP clients.",
    },
}

# Dynamic Client Registration (RFC 7591). Disabled by default: an open
# registration endpoint lets anyone create OAuth applications. Enable it only
# when remote MCP clients must self-register, and optionally require an initial
# access token presented as `Authorization: Bearer <token>`.
OAUTH2_DCR_ENABLED = os.getenv("OAUTH2_DCR_ENABLED", "false").lower() == "true"
OAUTH2_DCR_INITIAL_ACCESS_TOKEN = os.getenv("OAUTH2_DCR_INITIAL_ACCESS_TOKEN", "")

SPECTACULAR_SETTINGS = {
    "TITLE": "WYGIWYH API",
    "DESCRIPTION": "A no-frills expense tracker",
    "VERSION": "1.0.0",
    "SERVE_INCLUDE_SCHEMA": False,
    # OTHER SETTINGS
}

if "procrastinate" in sys.argv:
    LOGGING = {
        "version": 1,
        "disable_existing_loggers": True,
        "formatters": {
            "standard": {
                "format": "[%(asctime)s] - %(levelname)s - %(name)s - %(message)s",
                "datefmt": "%Y-%m-%d %H:%M:%S",
            },
        },
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "formatter": "standard",
                "level": "INFO",
            },
        },
        "root": {
            "handlers": ["console"],
            "level": "INFO",
        },
        "loggers": {
            "procrastinate": {
                "level": "INFO",
            },
        },
    }
else:
    LOGGING = {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "standard": {
                "format": "[%(asctime)s] - %(levelname)s - %(name)s - %(message)s",
                "datefmt": "%Y-%m-%d %H:%M:%S",
            },
        },
        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "formatter": "standard",
                "level": "INFO",
            },
        },
        "root": {
            "handlers": ["console"],
            "level": "INFO",
        },
        "loggers": {
            "procrastinate": {
                "handlers": [],
                "propagate": False,
            },
            "allauth": {
                "handlers": ["console"],
                "level": "DEBUG",
                "propagate": False,
            },
        },
    }

CACHALOT_UNCACHABLE_TABLES = ("django_migrations", "procrastinate_jobs")

# Procrastinate
PROCRASTINATE_ON_APP_READY = "apps.common.procrastinate.on_app_ready"

# PWA
PWA_APP_NAME = SITE_TITLE
PWA_APP_DESCRIPTION = "A simple and powerful finance tracker"
PWA_APP_THEME_COLOR = "#fbb700"
PWA_APP_BACKGROUND_COLOR = "#222222"
PWA_APP_DISPLAY = "standalone"
PWA_APP_SCOPE = "/"
PWA_APP_ORIENTATION = "any"
PWA_APP_START_URL = "/"
PWA_APP_STATUS_BAR_COLOR = "default"
PWA_APP_ICONS = [
    {"src": "/static/img/favicon/android-icon-192x192.png", "sizes": "192x192"}
]
PWA_APP_ICONS_APPLE = [
    {"src": "/static/img/favicon/apple-icon-180x180.png", "sizes": "180x180"}
]
PWA_APP_SPLASH_SCREEN = [
    {
        "src": "/static/img/pwa/splash-640x1136.png",
        "media": "(device-width: 320px) and (device-height: 568px) and (-webkit-device-pixel-ratio: 2)",
    }
]
PWA_APP_DIR = "ltr"
PWA_APP_LANG = "en-US"
PWA_APP_SHORTCUTS = [
    {
        "name": "New Transaction",
        "url": "/add/",
        "description": "Add new transaction",
    }
]
PWA_APP_SCREENSHOTS = [
    {
        "src": "/static/img/pwa/splash-750x1334.png",
        "sizes": "750x1334",
        "type": "image/png",
        "form_factor": "wide",
    },
    {
        "src": "/static/img/pwa/splash-750x1334.png",
        "sizes": "750x1334",
        "type": "image/png",
    },
]
PWA_SERVICE_WORKER_PATH = BASE_DIR / "templates" / "pwa" / "serviceworker.js"

ENABLE_SOFT_DELETE = os.getenv("ENABLE_SOFT_DELETE", "false").lower() == "true"
CHECK_FOR_UPDATES = os.getenv("CHECK_FOR_UPDATES", "true").lower() == "true"
KEEP_DELETED_TRANSACTIONS_FOR = int(os.getenv("KEEP_DELETED_ENTRIES_FOR", "365"))
APP_VERSION = os.getenv("APP_VERSION", "unknown")
DEMO = os.getenv("DEMO", "false").lower() == "true"
