# c3nav settings, mostly taken from the pretix project
import math
import os
import re
import string
import sys
from contextlib import suppress
from pathlib import Path
from typing import Optional

import django.conf.locale
from django.contrib.messages import constants as messages
from django.core.exceptions import ImproperlyConfigured
from django.utils.crypto import get_random_string
from django.utils.dateparse import parse_duration
from django.utils.translation import gettext_lazy as _
from pydantic.type_adapter import TypeAdapter
from pyproj import Proj, Transformer

from c3nav import __version__ as c3nav_version
from c3nav.api.schema import BaseSchema
from c3nav.mapdata.nocutils import NocLayersSchema
from c3nav.utils.config import C3navConfigParser
from c3nav.utils.environ import Env


def get_data_dir(setting: str, fallback: Path, create: bool = True, parents: bool = False,
                 config_section: str = 'c3nav', config_option: Optional[str] = None):
    if not config_option:
        config_option = setting.lower()
    subdir = config.get(config_section, config_option, fallback=None, env='C3NAV_' + setting)
    subdir = Path(subdir).resolve() if subdir else fallback
    if not subdir.exists():
        if create:
            subdir.mkdir(parents=parents)
        else:
            raise FileNotFoundError('The %s directory [%s] doesn\'t exist.' % (config_option, subdir))
    elif not subdir.is_dir():
        raise NotADirectoryError('The path set for the %s directory [%s] is not a directory.' % (config_option, subdir))
    return subdir


env = Env()
C3NAV_CONFIG = config = C3navConfigParser(env=env)
if 'C3NAV_CONFIG' in env:
    # if a config file is explicitly defined, make sure we can read it.
    env.path('C3NAV_CONFIG').open('r')
config.read(['/etc/c3nav/c3nav.cfg', os.path.expanduser('~/.c3nav.cfg'), env.str('C3NAV_CONFIG', 'c3nav.cfg')],
            encoding='utf-8')

INSTANCE_NAME = config.get('c3nav', 'name', fallback='', env='C3NAV_INSTANCE_NAME')

SENTRY_DSN = config.get('sentry', 'dsn', fallback=None, env='SENTRY_DSN')

with suppress(ImportError):
    if SENTRY_DSN:
        import sentry_sdk
        from sentry_sdk.integrations.celery import CeleryIntegration
        from sentry_sdk.integrations.django import DjangoIntegration
        from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber

        sensitive_env_vars = ['C3NAV_DJANGO_SECRET', 'C3NAV_TILE_SECRET', 'C3NAV_DATABASE', 'C3NAV_DATABASE_PASSWORD',
                              'C3NAV_MEMCACHED', 'C3NAV_MEMCACHED_USER', 'C3NAV_MEMCACHED_PASSWORD',
                              'C3NAV_REDIS', 'C3NAV_CELERY_BROKER', 'C3NAV_CELERY_BACKEND',
                              'C3NAV_EMAIL', 'C3NAV_EMAIL_PASSWORD']
        sensitive_vars = ['SECRET_KEY', 'TILE_SECRET_KEY', 'DATABASES', 'CACHES', 'BROKER_URL', 'CELERY_RESULT_BACKEND']

        denylist = DEFAULT_DENYLIST + sensitive_env_vars + sensitive_vars
        sentry_sdk.init(
            dsn=SENTRY_DSN,
            release=c3nav_version,
            integrations=[CeleryIntegration(), DjangoIntegration()],
            event_scrubber=EventScrubber(denylist=denylist),
            enable_tracing=bool(config.getfloat('sentry', 'traces_sample_rate', fallback=0.0)),
            traces_sample_rate=config.getfloat('sentry', 'traces_sample_rate', fallback=0.0),
        )

# Build paths inside the project like this: BASE_DIR / 'something'
PROJECT_DIR = Path(__file__).resolve().parent
BASE_DIR = PROJECT_DIR.parent
DATA_DIR = get_data_dir('DATA_DIR', BASE_DIR / 'data', parents=True, config_option='datadir')
LOG_DIR = get_data_dir('LOG_DIR', DATA_DIR / 'logs', config_option='logdir')
MEDIA_ROOT = get_data_dir('MEDIA_ROOT', DATA_DIR / 'media', config_section='django')
STATIC_ROOT = get_data_dir('STATIC_ROOT', PROJECT_DIR / 'static.dist', config_section='django')
SOURCES_ROOT = get_data_dir('SOURCES_ROOT', DATA_DIR / 'sources')
MAP_ROOT = get_data_dir('MAP_ROOT', DATA_DIR / 'map')
RENDER_ROOT = get_data_dir('RENDER_ROOT', DATA_DIR / 'render')
TILES_ROOT = get_data_dir('TILES_ROOT', DATA_DIR / 'tiles')
CACHE_ROOT = get_data_dir('CACHE_ROOT', DATA_DIR / 'cache')
STATS_ROOT = get_data_dir('STATS_ROOT', DATA_DIR / 'stats')
PREVIEWS_ROOT = get_data_dir('PREVIEWS_ROOT', DATA_DIR / 'previews')

# override the matplotlib default config directory if it's not configured
os.environ.setdefault('MPLCONFIGDIR', str(get_data_dir('MPLCONFIGDIR', CACHE_ROOT / 'matplotlib')))

PUBLIC_EDITOR = config.getboolean('c3nav', 'editor', fallback=True)
PUBLIC_BASE_MAPDATA = config.getboolean('c3nav', 'public_base_mapdata', fallback=False)
AUTO_PROCESS_UPDATES = config.getboolean('c3nav', 'auto_process_updates', fallback=True)

RANDOM_LOCATION_GROUPS = config.getlist('c3nav', 'random_location_groups', fallback=None)
if RANDOM_LOCATION_GROUPS:
    RANDOM_LOCATION_GROUPS = tuple(int(i) for i in RANDOM_LOCATION_GROUPS)

SECRET_KEY = config.get('django', 'secret', fallback=None)
if not SECRET_KEY:
    SECRET_FILE = config.get('django', 'secret_file', fallback=None)
    if SECRET_FILE:
        SECRET_FILE = Path(SECRET_FILE)
    else:
        SECRET_FILE = DATA_DIR / '.secret'
    if SECRET_FILE.exists():
        with open(SECRET_FILE, 'r') as f:
            SECRET_KEY = f.read().strip()
    else:
        SECRET_KEY = get_random_string(50, string.printable)
        with open(SECRET_FILE, 'w') as f:
            os.chmod(SECRET_FILE, 0o600)
            try:
                os.chown(SECRET_FILE, os.getuid(), os.getgid())
            except AttributeError:
                pass
            f.write(SECRET_KEY)

SECRET_TILE_KEY = config.get('c3nav', 'tile_secret', fallback=None)
if not SECRET_TILE_KEY:
    SECRET_TILE_FILE = config.get('c3nav', 'tile_secret_file', fallback=None)
    if SECRET_TILE_FILE:
        SECRET_TILE_FILE = Path(SECRET_TILE_FILE)
    else:
        SECRET_TILE_FILE = DATA_DIR / '.tile_secret'
    if SECRET_TILE_FILE.exists():
        with open(SECRET_TILE_FILE, 'r') as f:
            SECRET_TILE_KEY = f.read().strip()
    else:
        SECRET_TILE_KEY = get_random_string(50, string.printable)
        with open(SECRET_TILE_FILE, 'w') as f:
            os.chmod(SECRET_TILE_FILE, 0o600)
            try:
                os.chown(SECRET_TILE_FILE, os.getuid(), os.getgid())
            except AttributeError:
                pass
            f.write(SECRET_TILE_KEY)

SECRET_MESH_KEY = config.get('c3nav', 'mesh_secret', fallback=None)
if not SECRET_MESH_KEY:
    SECRET_MESH_FILE = config.get('c3nav', 'mesh_secret_file', fallback=None)
    if SECRET_MESH_FILE:
        SECRET_MESH_FILE = Path(SECRET_MESH_FILE)
    else:
        SECRET_MESH_FILE = DATA_DIR / '.mesh_secret'
    if SECRET_MESH_FILE.exists():
        with open(SECRET_MESH_FILE, 'r') as f:
            SECRET_MESH_KEY = f.read().strip()
    else:
        SECRET_MESH_KEY = get_random_string(50, string.printable)
        with open(SECRET_MESH_FILE, 'w') as f:
            os.chmod(SECRET_MESH_FILE, 0o600)
            try:
                os.chown(SECRET_MESH_FILE, os.getuid(), os.getgid())
            except AttributeError:
                pass
            f.write(SECRET_MESH_KEY)

# Adjustable settings

debug_fallback = "runserver" in sys.argv
DEBUG = config.getboolean('django', 'debug', fallback=debug_fallback, env='C3NAV_DEBUG')

BRANDING = config.get('c3nav', 'branding', fallback='c3nav')
APP_ENABLED = config.getboolean('c3nav', 'app_enabled', fallback=False)

ENABLE_MESH = config.getboolean('c3nav', 'enable_mesh', fallback=True, env='ENABLE_MESH')
SERVE_ANYTHING = config.getboolean('c3nav', 'serve_anything', fallback=True, env='SERVE_ANYTHING')
SERVE_API = config.getboolean('c3nav', 'serve_api', fallback=SERVE_ANYTHING, env='SERVE_API')

# how many location lookups to cache in each worker's in-memory LRU cache proxy
CACHE_SIZE_LOCATIONS = config.getint('c3nav', 'cache_size_locations', fallback=128)
CACHE_SIZE_API = config.getint('c3nav', 'cache_size_api', fallback=64)

RENDER_SCALE = config.getfloat('c3nav', 'render_scale', fallback=20.0)
IMAGE_RENDERER = config.get('c3nav', 'image_renderer', fallback='svg')
SVG_RENDERER = config.get('c3nav', 'svg_renderer', fallback='rsvg-convert')

CACHE_TILES = config.getboolean('c3nav', 'cache_tiles', fallback=not DEBUG)
CACHE_PREVIEWS = config.getboolean('c3nav', 'cache_previews', fallback=not DEBUG)
CACHE_RESOLUTION = config.getint('c3nav', 'cache_resolution', fallback=4)

IMPRINT_LINK = config.get('c3nav', 'imprint_link', fallback=None)
IMPRINT_PATRONS = config.get('c3nav', 'imprint_patrons', fallback=None)
IMPRINT_TEAM = config.get('c3nav', 'imprint_team', fallback=None)
IMPRINT_HOSTING = config.get('c3nav', 'imprint_hosting', fallback=None)
ABOUT_EXTRA = config.get('c3nav', 'about_extra', fallback=None)

INITIAL_LEVEL = config.get('c3nav', 'initial_level', fallback=None)
INITIAL_BOUNDS = config.get('c3nav', 'initial_bounds', fallback='').split(' ')

GRID_ROWS = config.get('c3nav', 'grid_rows', fallback=None)
GRID_COLS = config.get('c3nav', 'grid_cols', fallback=None)

MAIN_PREVIEW_SLUG = config.get('c3nav', 'main_preview_slug', fallback='level-0')

if len(INITIAL_BOUNDS) == 4:
    try:
        INITIAL_BOUNDS = tuple(float(i) for i in INITIAL_BOUNDS)
    except ValueError:
        INITIAL_BOUNDS = None
else:
    INITIAL_BOUNDS = None

HUB_API_BASE = config.get('c3nav', 'hub_api_base', fallback='').removesuffix('/')
HUB_API_SECRET = config.get('c3nav', 'hub_api_secret', fallback='')
if not HUB_API_SECRET:
    HUB_API_SECRET_FILE = config.get('c3nav', 'hub_api_secret_file', fallback=None)
    if HUB_API_SECRET_FILE:
        HUB_API_SECRET_FILE = Path(HUB_API_SECRET_FILE)
    else:
        HUB_API_SECRET_FILE = DATA_DIR / '.hub_api_secret'
    if HUB_API_SECRET_FILE.exists():
        HUB_API_SECRET = HUB_API_SECRET_FILE.read_text().strip()

NOC_BASE = config.get('c3nav', 'noc_base', fallback='').removesuffix('/')
NOC_LAYERS = NocLayersSchema.validate_json(config.get('c3nav', 'noc_layers', fallback='{}'))

_db_backend = config.get('database', 'backend', fallback='sqlite3')
DATABASES: dict[str, dict[str, str | int | Path]] = {
    'default': env.db_url('C3NAV_DATABASE') if 'C3NAV_DATABASE' in env else {
        'ENGINE': _db_backend if '.' in _db_backend else 'django.db.backends.' + _db_backend,
    }
}
for key in ('NAME', 'USER', 'PASSWORD', 'HOST', 'PORT'):
    if 'C3NAV_DATABASE' in env:
        # if the C3NAV_DATABASE is present all database options in the config files are ignored
        value = env.str('C3NAV_DATABASE_' + key, default=None)
    else:
        value = config.get('database', key.lower(), fallback=None)
    if value:
        DATABASES['default'][key] = value
    elif key == 'NAME':
        DATABASES['default'].setdefault(key, DATA_DIR / 'db.sqlite3' if _db_backend.endswith('sqlite3')
                                        else (f'c3nav_{INSTANCE_NAME}' if INSTANCE_NAME else 'c3nav'))

DATABASES['default'].setdefault('CONN_MAX_AGE',
                                config.getint('database', 'conn_max_age',
                                              fallback=(0 if _db_backend.endswith('sqlite3') else 120)))
DATABASES['default'].setdefault('CONN_HEALTH_CHECKS', not _db_backend.endswith('sqlite3'))

DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'

STATIC_URL = config.get('django', 'static_url', fallback='/static/', env='C3NAV_STATIC_URL')
MEDIA_URL = config.get('django', 'media_url', fallback='/media/', env='C3NAV_MEDIA_URL')

ALLOWED_HOSTS = config.getlist('django', 'allowed_hosts', fallback='*')

if config.getboolean('django', 'reverse_proxy', fallback=False):
    SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")


LANGUAGE_CODE = config.get('locale', 'default', fallback='en', env='C3NAV_DEFAULT_LOCALE')
TIME_ZONE = config.get('locale', 'timezone', fallback='UTC', env='C3NAV_TIMEZONE')

MAIL_FROM = SERVER_EMAIL = DEFAULT_FROM_EMAIL = config.get('email', 'from', fallback='c3nav@localhost')
EMAIL_HOST = config.get('email', 'host', fallback='' if DEBUG else 'localhost')
EMAIL_PORT = config.getint('email', 'port', fallback=25)
EMAIL_HOST_USER = config.get('email', 'user', fallback='')
EMAIL_HOST_PASSWORD = config.get('email', 'password', fallback='')
EMAIL_USE_TLS = config.getboolean('email', 'tls', fallback=False)
EMAIL_USE_SSL = config.getboolean('email', 'ssl', fallback=False)
EMAIL_BACKEND = config.get(
    'email', 'ssl',
    fallback='django.core.mail.backends.' + ('smtp' if EMAIL_HOST else 'console') + '.EmailBackend',
)
if 'C3NAV_EMAIL' in env:
    vars().update(env.email_url('C3NAV_EMAIL'))
EMAIL_SUBJECT_PREFIX = ('[c3nav-%s] ' % INSTANCE_NAME) if INSTANCE_NAME else '[c3nav]'
if config.has_section('mail'):
    raise ImproperlyConfigured('mail config section got renamed to email. Please fix your config file.')

ADMINS = [('Admin', n) for n in config.getlist('mail', 'admins', fallback='')]

CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
        'LOCATION': 'unique-snowflake',
    }
}
HAS_REAL_CACHE = False

SESSION_ENGINE = "django.contrib.sessions.backends.db"

HAS_MEMCACHED = bool(config.get('memcached', 'location', fallback=None, env='C3NAV_MEMCACHED'))
if HAS_MEMCACHED:
    HAS_REAL_CACHE = True
    CACHES['default'] = {
        'BACKEND': 'django.core.cache.backends.memcached.PyLibMCCache',
        'LOCATION': config.get('memcached', 'location', env='C3NAV_MEMCACHED'),
        'OPTIONS': {
            'username': config.get('memcached', 'username', fallback=None),
            'password': config.get('memcached', 'password', fallback=None),
        }
    }
    SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"

HAS_REDIS = bool(config.get('redis', 'location', fallback=None, env='C3NAV_REDIS'))
REDIS_CONNECTION_POOL = None
if HAS_REDIS:
    import redis
    HAS_REAL_CACHE = True
    REDIS_SERVERS = config.getlist('redis', 'location', env='C3NAV_REDIS')
    CACHES['redis'] = {
        "BACKEND": "django.core.cache.backends.redis.RedisCache",
        "LOCATION": REDIS_SERVERS,
    }
    if not HAS_MEMCACHED:
        CACHES['default'] = CACHES['redis']
    else:
        SESSION_CACHE_ALIAS = "redis"
    SESSION_ENGINE = "django.contrib.sessions.backends.cache"

    REDIS_CONNECTION_POOL = redis.ConnectionPool.from_url(REDIS_SERVERS[0])

HAS_CELERY = bool(config.get('celery', 'broker', fallback=None))
if HAS_CELERY:
    BROKER_URL = config.get('celery', 'broker')
    CELERY_RESULT_BACKEND = config.get('celery', 'backend')
    CELERY_SEND_TASK_ERROR_EMAILS = bool(ADMINS)
else:
    CELERY_ALWAYS_EAGER = True
CELERY_TASK_SERIALIZER = 'json'
CELERY_ACCEPT_CONTENT = ['json']
CELERY_RESULT_SERIALIZER = 'json'

TILE_CACHE_SERVER = config.get('c3nav', 'tile_cache_server', fallback=None)

# Internal settings

SESSION_COOKIE_NAME = 'c3nav_session'
SESSION_COOKIE_DOMAIN = config.get('c3nav', 'session_cookie_domain', fallback=None)
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = not DEBUG
SESSION_COOKIE_SAMESITE = 'none' if SESSION_COOKIE_SECURE else 'lax'

LANGUAGE_COOKIE_NAME = 'c3nav_language'

CSRF_COOKIE_NAME = 'c3nav_csrftoken'
CSRF_COOKIE_SECURE = not DEBUG

TILE_ACCESS_COOKIE_NAME = 'c3nav_tile_access'
TILE_ACCESS_COOKIE_DOMAIN = config.get('c3nav', 'tile_access_cookie_domain', fallback=None)
TILE_ACCESS_COOKIE_HTTPONLY = True
TILE_ACCESS_COOKIE_SECURE = not DEBUG
TILE_ACCESS_COOKIE_SAMESITE = 'none' if SESSION_COOKIE_SECURE else 'lax'


SSO_ENABLED = config.getboolean('sso', 'enabled', fallback=False)


# Application definition

INSTALLED_APPS = [
    *(["daphne"] if DEBUG else []),
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'channels',
    'compressor',
    'bootstrap3',
    *(['social_django'] if SSO_ENABLED else []),
    *(["ninja"] if SERVE_API else []),
    'c3nav.api',
    'c3nav.mapdata',
    'c3nav.routing',
    'c3nav.site',
    'c3nav.control',
    *(["c3nav.mesh"] if ENABLE_MESH else []),
    'c3nav.editor',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'c3nav.mapdata.middleware.NoLanguageMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    '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',
    'c3nav.mapdata.middleware.UserDataMiddleware',
    'c3nav.site.middleware.MobileclientMiddleware',
    'c3nav.control.middleware.UserPermissionsMiddleware',
    # 'c3nav.api.middleware.JsonRequestBodyMiddleware',  # might still be needed in editor
]

with suppress(ImportError):
    import debug_toolbar  # noqa
    INSTALLED_APPS.append('debug_toolbar')
    MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')

with suppress(ImportError):
    import htmlmin  # noqa
    MIDDLEWARE += [
        'htmlmin.middleware.HtmlMinifyMiddleware',
        'htmlmin.middleware.MarkRequestMiddleware',
    ]

with suppress(ImportError):
    import django_extensions  # noqa
    INSTALLED_APPS.append('django_extensions')

METRICS = config.getboolean('c3nav', 'metrics', fallback=False)
if METRICS:
    try:
        import django_prometheus  # noqa
        INSTALLED_APPS.append('django_prometheus')
        MIDDLEWARE = [
            'django_prometheus.middleware.PrometheusBeforeMiddleware',
            *MIDDLEWARE,
            'django_prometheus.middleware.PrometheusAfterMiddleware',
        ]
    except ImportError:
        METRICS = False

# Security settings
X_FRAME_OPTIONS = 'DENY'

# URL settings
ROOT_URLCONF = 'c3nav.urls'

WSGI_APPLICATION = 'c3nav.wsgi.application'
ASGI_APPLICATION = 'c3nav.asgi.application'

if HAS_REDIS:
    CHANNEL_LAYERS = {
        'default': {
            'BACKEND': 'channels_redis.core.RedisChannelLayer',
            'CONFIG': {
                "hosts": config.getlist('redis', 'location', env='C3NAV_REDIS'),
            },
        },
    }
else:
    CHANNEL_LAYERS = {
        "default": {
            "BACKEND": "channels.layers.InMemoryChannelLayer"
        }
    }

USE_I18N = True
USE_L10N = True
USE_TZ = True

NINJA_PAGINATION_CLASS = "ninja.pagination.LimitOffsetPagination"

LOCALE_PATHS = (
    PROJECT_DIR / 'locale',
)

EXTRA_LANG_INFO = {
    'en-UW': {
        'bidi': False,
        'code': 'en-UW',
        'name': 'Engwish UwU',
        'name_local': u'Engwish UwU', #unicode codepoints here
    },
}

# Add custom languages not provided by Django
LANG_INFO = dict(django.conf.locale.LANG_INFO, **EXTRA_LANG_INFO)
django.conf.locale.LANG_INFO = LANG_INFO

SELECTED_LANGUAGES = frozenset(config.getlist('locale', 'languages', fallback='en,de',
                                              env='C3NAV_LANGUAGES'))
LANGUAGES = [(code, name) for code, name in [
    ('en', _('English')),
    ('en-UW', _('Engwish UwU')),
    ('de', _('German')),
] if code in SELECTED_LANGUAGES]

template_loaders = (
    'django.template.loaders.filesystem.Loader',
    'django.template.loaders.app_directories.Loader',
)
if not DEBUG:
    template_loaders = (
        ('django.template.loaders.cached.Loader', template_loaders),
    )
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [],
        'OPTIONS': {
            'context_processors': [
                'django.contrib.auth.context_processors.auth',
                'django.template.context_processors.debug',
                'django.template.context_processors.i18n',
                'django.template.context_processors.request',
                'django.contrib.messages.context_processors.messages',
                'c3nav.site.context_processors.logos',
                'c3nav.site.context_processors.user_data_json',
                'c3nav.site.context_processors.theme',
                'c3nav.site.context_processors.header_logo_mask',
            ],
            'loaders': template_loaders
        },
    },
]


STATICFILES_FINDERS = (
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
    'compressor.finders.CompressorFinder',
    'c3nav.site.finders.LogoFinder',
)

BOOTSTRAP3 = {
    'success_css_class': '',
}

STATICFILES_DIRS = [
    BASE_DIR / 'c3nav' / 'static',
    *config.getlist('c3nav', 'extra_static_dirs', fallback=''),
]

COMPRESS_PRECOMPILERS = (
    ('text/x-scss', 'django_libsass.SassCompiler'),
)

COMPRESS_ENABLED = COMPRESS_OFFLINE = not debug_fallback

COMPRESS_CSS_FILTERS = (
    'compressor.filters.css_default.CssAbsoluteFilter',
    'compressor.filters.cssmin.CSSCompressorFilter',
)

COMPRESS_CSS_HASHING_METHOD = 'content'

HEADER_LOGO = config.get('c3nav', 'header_logo', fallback=None)
HEADER_LOGO_MASK_MODE = config.get('c3nav', 'header_logo_mask_mode', fallback=None)
FAVICON = config.get('c3nav', 'favicon', fallback=None)
FAVICON_PACKAGE = config.get('c3nav', 'favicon_package', fallback=None)

PRIMARY_COLOR_RANDOMISATION = {
    'mode': config.get('primary_color_randomization', 'mode', fallback='off'),
    'duration': parse_duration(config.get('primary_color_randomization', 'duration', fallback='1:00')),
    'chroma': float(config.get('primary_color_randomization', 'chroma', fallback='0.5')),
    'lightness': float(config.get('primary_color_randomization', 'lightness', fallback='0.3')),
}


def oklch_to_oklab(L, C, h):
    from math import cos, sin
    a = C * cos(h)
    b = C * sin(h)
    return L, a, b


def clamp(x, low, high):
    return min(max(x, low), high)


def oklab_to_linear_rgb(L, a, b):
    """
    see https://bottosson.github.io/posts/oklab/
    """
    l_ = L + 0.3963377774 * a + 0.2158037573 * b
    m_ = L - 0.1055613458 * a - 0.0638541728 * b
    s_ = L - 0.0894841775 * a - 1.2914855480 * b

    l = l_ * l_ * l_
    m = m_ * m_ * m_
    s = s_ * s_ * s_

    return (
        clamp(+4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s, 0, 1),
        clamp(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s, 0, 1),
        clamp(-0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s, 0, 1),
    )


def linear_to_s(linear):
    if linear <= 0.0031308:
        return linear * 12.92
    else:
        return 1.055 * pow(linear, 1.0 / 2.4) - 0.055


def linear_rgb_to_srgb(r, g, b):
    return linear_to_s(r), linear_to_s(g), linear_to_s(b)


def hex_from_oklch(L, C, h):
    oklab = oklch_to_oklab(L, C, h)
    linear_rgb = oklab_to_linear_rgb(*oklab)
    srgb = linear_rgb_to_srgb(*linear_rgb)
    srgb255 = tuple(int(round(x * 255, 0)) for x in srgb)
    hex = '#%0.2X%0.2X%0.2X' % srgb255
    return hex


RANDOM_PRIMARY_COLOR_LIST = [hex_from_oklch(PRIMARY_COLOR_RANDOMISATION['lightness'],
                                            PRIMARY_COLOR_RANDOMISATION['chroma'],
                                            x) for x in range(0, 360)]

DEFAULT_THEME = config.getint('c3nav', 'default_theme', fallback=0)
BASE_THEME = {
    'is_dark': config.getboolean('theme', 'is_dark', fallback=False),
    'randomize_primary_color': config.getboolean('theme', 'randomize_primary_color', fallback=False),
    'map': {
        'background': config.get('theme', 'map_background', fallback='#dcdcdc'),
        'wall_fill': config.get('theme', 'map_wall_fill', fallback='#aaaaaa'),
        'wall_border': config.get('theme', 'map_wall_border', fallback='#666666'),
        'door_fill': config.get('theme', 'map_door_fill', fallback='#ffffff'),
        'ground_fill': config.get('theme', 'map_ground_fill', fallback='#eeeeee'),
        'obstacles_default_fill': config.get('theme', 'map_obstacles_default_fill', fallback='#b7b7b7'),
        'obstacles_default_border': config.get('theme', 'map_obstacles_default_border', fallback='#888888'),
        'highlight': config.get('theme', 'css_primary', fallback='#9b4dca'),
    },
    'css_vars': {
        'initial': config.get('theme', 'css_initial', fallback='#ffffff'),
        'primary': config.get('theme', 'css_primary', fallback='#9b4dca'),
        'logo': config.get('theme', 'css_logo', fallback=None),
        'secondary': config.get('theme', 'css_secondary', fallback='#525862'),
        'tertiary': config.get('theme', 'css_tertiary', fallback='#f0f0f0'),
        'quaternary': config.get('theme', 'css_quaternary', fallback='#767676'),
        'quinary': config.get('theme', 'css_quinary', fallback='#cccccc'),
        'header-text': config.get('theme', 'css_header_text', fallback='#ffffff'),
        'header-text-hover': config.get('theme', 'css_header_text_hover', fallback='#eeeeee'),
        'header-background': config.get('theme', 'css_header_background', fallback='#000000'),
        'shadow': config.get('theme', 'css_shadow', fallback='#000000'),
        'overlay-background': config.get('theme', 'css_overlay_background', fallback='#ffffff'),
        'grid': config.get('theme', 'css_grid', fallback='#000000'),
        'modal-backdrop': config.get('theme', 'css_modal_backdrop', fallback='#000000'),
        'route-dots-shadow': config.get('theme', 'css_route_dots_shadow', fallback='#ffffff'),
        'leaflet-background': config.get('theme', 'map_background', fallback='#dcdcdc'),
    }
}

WIFI_SSIDS = config.getlist('c3nav', 'wifi_ssids', fallback='')


# Projection
PROJECTION_PROJ4 = config.get('projection', 'proj4', fallback=None)
PROJECTION_ZERO_POINT = config.get('projection', 'zero_point', fallback=None)
PROJECTION_ZERO_POINT_IS_WGS84 = '°' in PROJECTION_ZERO_POINT if PROJECTION_ZERO_POINT else False
PROJECTION_ROTATION = config.getfloat('projection', 'rotation', fallback=0.0)
PROJECTION_ROTATION_MATRIX = config.get('projection', 'rotation_matrix', fallback=None)
PROJECTION_TRANSFORMER: Optional[Transformer] = None
PROJECTION_TRANSFORMER_STRING: Optional[str] = None

if PROJECTION_PROJ4:
    if '+units=m' not in PROJECTION_PROJ4:
        PROJECTION_PROJ4 += ' +units=m'
    PROJECTION_TRANSFORMER_STRING = re.sub(r'\s?\+no_defs', '', PROJECTION_PROJ4)

    if (PROJECTION_ZERO_POINT or PROJECTION_ROTATION) and 'pipeline' not in PROJECTION_TRANSFORMER_STRING:
        PROJECTION_TRANSFORMER_STRING = f'+proj=pipeline +step {PROJECTION_TRANSFORMER_STRING}'

    if PROJECTION_ZERO_POINT:
        PROJECTION_ZERO_POINT = tuple((float(i) for i in PROJECTION_ZERO_POINT.split(',')))
        if len(PROJECTION_ZERO_POINT) != 2:
            raise ImproperlyConfigured(f'invalid projection zero point "{PROJECTION_ZERO_POINT!r}"')
        if PROJECTION_ZERO_POINT_IS_WGS84:
            PROJECTION_ZERO_POINT = Proj.from_pipeline(PROJECTION_PROJ4).transform(PROJECTION_ZERO_POINT[0],
                                                                                   PROJECTION_ZERO_POINT[1])
        PROJECTION_TRANSFORMER_STRING += (f' +step +proj=affine +xoff=-{PROJECTION_ZERO_POINT[0]} '
                                          f'+yoff=-{PROJECTION_ZERO_POINT[1]}')

    if PROJECTION_ROTATION != 0:
        PROJECTION_ROTATION_MATRIX = (
            math.cos(math.radians(PROJECTION_ROTATION)), math.sin(math.radians(PROJECTION_ROTATION)), 0, 0,
            -math.sin(math.radians(PROJECTION_ROTATION)), math.cos(math.radians(PROJECTION_ROTATION)), 0, 0,
            0, 0, 1, 0,
            0, 0, 0, 1
        )
    elif PROJECTION_ROTATION_MATRIX:
        PROJECTION_ROTATION_MATRIX = tuple((float(i) for i in PROJECTION_ROTATION_MATRIX.split(',')))
        if len(PROJECTION_ROTATION_MATRIX) != 16:
            raise ImproperlyConfigured(f'invalid rotation matrix "{PROJECTION_ROTATION_MATRIX!r}"')

    if PROJECTION_ROTATION_MATRIX:
        PROJECTION_TRANSFORMER_STRING += (
            f' +step +proj=affine '
            f'+s11={PROJECTION_ROTATION_MATRIX[0]} +s12={PROJECTION_ROTATION_MATRIX[1]}'
        )
        if PROJECTION_ROTATION_MATRIX[2] != 0:
            PROJECTION_TRANSFORMER_STRING += f' +s13={PROJECTION_ROTATION_MATRIX[2]}'
        PROJECTION_TRANSFORMER_STRING += f' +s21={PROJECTION_ROTATION_MATRIX[4]} +s22={PROJECTION_ROTATION_MATRIX[5]}'
        if PROJECTION_ROTATION_MATRIX[6] != 0:
            PROJECTION_TRANSFORMER_STRING += ' +s23={PROJECTION_ROTATION_MATRIX[6]}'
        if PROJECTION_ROTATION_MATRIX[8] != 0:
            PROJECTION_TRANSFORMER_STRING += f' +s31={PROJECTION_ROTATION_MATRIX[8]}'
        if PROJECTION_ROTATION_MATRIX[9] != 0:
            PROJECTION_TRANSFORMER_STRING += f' +s32={PROJECTION_ROTATION_MATRIX[9]}'
        if PROJECTION_ROTATION_MATRIX[10] != 1:
            PROJECTION_TRANSFORMER_STRING += f' +s33={PROJECTION_ROTATION_MATRIX[10]}'
        if PROJECTION_ROTATION_MATRIX[15] != 1:
            PROJECTION_TRANSFORMER_STRING += f' +tscale={PROJECTION_ROTATION_MATRIX[15]}'

    PROJECTION_TRANSFORMER_STRING += ' +no_defs'
    PROJECTION_TRANSFORMER = Proj.from_pipeline(PROJECTION_TRANSFORMER_STRING)

USER_REGISTRATION = config.getboolean('c3nav', 'user_registration', fallback=True)

INTERNAL_IPS = ('127.0.0.1', '::1')

MESSAGE_TAGS = {
    messages.INFO: 'alert-info',
    messages.ERROR: 'alert-danger',
    messages.WARNING: 'alert-warning',
    messages.SUCCESS: 'alert-success',
}
MESSAGE_STORAGE = 'django.contrib.messages.storage.session.SessionStorage'

SILENCED_SYSTEM_CHECKS = ['debug_toolbar.W006']

loglevel = env.str('C3NAV_LOGLEVEL', default='DEBUG' if DEBUG else 'INFO').upper()

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'default': {
            'format': '%(levelname)s %(asctime)s %(name)s %(module)s %(message)s'
        },
    },
    'handlers': {
        'console': {
            'level': loglevel,
            'class': 'logging.StreamHandler',
            'formatter': 'default'
        },
        'file': {
            'level': loglevel,
            'class': 'logging.FileHandler',
            'filename': LOG_DIR / 'c3nav.log',
            'formatter': 'default'
        }
    },
    'loggers': {
        '': {
            'handlers': ['file', 'console'],
            'level': loglevel,
            'propagate': True,
        },
        'django.request': {
            'handlers': ['file', 'console'],
            'level': loglevel,
            'propagate': True,
        },
        'django.security': {
            'handlers': ['file', 'console'],
            'level': loglevel,
            'propagate': True,
        },
        'django.db.backends': {
            'handlers': ['file', 'console'],
            'level': 'INFO',  # Do not output all the queries
            'propagate': True,
        },
        'shapely.geos': {
            'handlers': ['file', 'console'],
            'level': 'INFO',
            'propagate': True,
        },
        'daphne.ws_protocol': {
            'handlers': ['file', 'console'],
            'level': 'INFO',  # Do not output all communication
            'propagate': True,
        },
    },
}

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


# SSO

SOCIAL_AUTH_STRATEGY = 'c3nav.control.sso.C3navStrategy'
SOCIAL_AUTH_JSONFIELD_ENABLED = DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql'

AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
)

if SSO_ENABLED:
    # add the enabled authentication backends to AUTHENTICATION_BACKENDS
    # we need this despite our own strategy looking it up directly because the backends context processor of
    # social_django directly uses the django setting without asking the normal config pipeline
    AUTHENTICATION_BACKENDS = (
        * config.getlist('sso', 'authentication_backends', fallback=''),
        *AUTHENTICATION_BACKENDS,
    )

SOCIAL_AUTH_PIPELINE = (
    'social_core.pipeline.social_auth.social_details',
    'social_core.pipeline.social_auth.social_uid',
    'social_core.pipeline.social_auth.auth_allowed',
    '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',
    'c3nav.control.sso.pipeline.access_permissions',
)
