import enum
import os
import sys
import socket
import re

import mini_buildd
import mini_buildd.files

#: This should never ever be changed
CHAR_ENCODING = "UTF-8"

#: Compute python-version dependent install path
PY_PACKAGE_PATH = f"/usr/lib/python{sys.version_info[0]}/dist-packages"

#: Global constant object to use when signalling shutdown via message queues
SHUTDOWN = "SHUTDOWN"

#: HTTPD log file names
LOG_FILE = "daemon.log"
ACCESS_LOG_FILE = "access.log"

#: (Debian package) path for the internal package templates
PACKAGE_TEMPLATES = "/usr/share/mini-buildd/package-templates"

#: Sample message used for instance handshakes
HANDSHAKE_MESSAGE = "Hand Shake"


class AuthType(enum.Enum):
    NONE = enum.auto()
    LOGIN = enum.auto()
    STAFF = enum.auto()
    ADMIN = enum.auto()


class Auth():
    def __init__(self, auth):
        self.auth = auth

    def __str__(self):
        return self.auth.name

    def is_authorized(self, user):
        """Check if django user is authorized."""
        def active_login():
            return user.is_authenticated and user.is_active

        if self.auth == AuthType.NONE:
            return True
        if user is None:
            return False
        if self.auth == AuthType.LOGIN and active_login():
            return True
        if self.auth == AuthType.STAFF and active_login() and user.is_staff:
            return True
        if self.auth == AuthType.ADMIN and active_login() and user.is_superuser:
            return True
        return False


#: Auth shortcuts
AUTH_NONE = Auth(AuthType.NONE)
AUTH_LOGIN = Auth(AuthType.LOGIN)
AUTH_STAFF = Auth(AuthType.STAFF)
AUTH_ADMIN = Auth(AuthType.ADMIN)


class Uri:
    """URI string with some convenience functionality."""

    DJANGO_PATH_REGEX = r"(?:(?P<path>.*))?"

    def __init__(self, uri, auth=AUTH_NONE, with_index=False, cache_ttl=0, regex=None, django_with_path=False):
        self.uri = uri

        #: Auth
        self.auth = auth

        #: Enable directory listing
        self.with_index = with_index

        #: Browser cache TTL in seconds (0 for no cache)
        self.cache_ttl = cache_ttl

        #: Restrict access via regex
        if regex is not None:
            self.regex = re.compile(r"^" + self.join(regex))
        else:
            self.regex = regex

        #: Non-static: Add 'path' to django URI
        self.django_with_path = django_with_path

    def __str__(self):
        return self.uri

    def django(self):
        return self.uri.lstrip("/") + (self.DJANGO_PATH_REGEX if self.django_with_path else "")

    def twisted(self):
        return self.uri.strip("/")

    def join(self, *args, prefix=""):
        return os.path.join(prefix, self.uri, *args)

    def url_join(self, *args, endpoint=None):
        ep = mini_buildd.http_endpoint() if endpoint is None else endpoint
        return ep.geturl(path=self.join(*args))

    @classmethod
    def uri2view(cls, uri):
        return uri.replace("static", "mini_buildd", 1)

    def to_view(self):
        return Uri(self.uri2view(self.uri))


#: Static URI dict (by route name)
#: 'view': The default (django).
#: 'dir': Directory Listing (django).
#: 'static': Static delivery (twisted).
URIS = {
    # Note (cache_ttl=0): If browser cache should be enabled at some point, then here. However, we have no "hashed-on-change" support and it's confusing if
    # 'though F5, js still running old code'. Also a real PITA in most browsers to clear the cache.
    "static": {
        "static": Uri("/static/static/", cache_ttl=0),
    },
    "home": {
        "view": Uri("/mini_buildd/"),
    },
    "setup": {
        "view": Uri("/mini_buildd/setup/"),
    },
    "events": {
        "view": Uri("/mini_buildd/events/", django_with_path=True),
        "dir": Uri("/mini_buildd/events-dir/", django_with_path=True),
        "static": Uri("/static/events/", with_index=True),
        "attach": Uri("/static/events.attach"),
    },
    "builds": {
        "view": Uri("/mini_buildd/builds/", django_with_path=True),
        "dir": Uri("/mini_buildd/builds-dir/", django_with_path=True),
        "static": Uri("/static/builds/", with_index=True),
    },
    "repositories": {
        "view": Uri("/mini_buildd/repositories/", django_with_path=True),
        "dir": Uri("/mini_buildd/repositories-dir/", django_with_path=True),
        "static": Uri("/static/repositories/", with_index=True, regex=r"(?![^/]+/(conf/.*|db/.*))"),
        "static_v10x": Uri("/repositories/", with_index=True, regex=r"(?![^/]+/(conf/.*|db/.*))"),
    },
    "builders": {
        "view": Uri("/mini_buildd/builders/"),
    },
    "crontab": {
        "view": Uri("/mini_buildd/crontab/"),
    },
    "api": {
        "view": Uri("/mini_buildd/api/"),
    },
    "log": {
        "view": Uri("/mini_buildd/log/", auth=AUTH_STAFF, django_with_path=True),
        "static": Uri("/static/log/", auth=AUTH_STAFF, with_index=True),
    },
    "admin": {
        "view": Uri("/admin/"),
    },
    "manual": {
        "static": Uri("/static/manual/"),
        "view": Uri("/mini_buildd/manual/"),
    },
    "sitemap": {
        "view": Uri("/mini_buildd/sitemap/"),
    },
    "accounts": {
        "base": Uri("/accounts/"),               # used to include django standard auth urls under, see urls.py
        "login": Uri("/accounts/login/"),        # is standard django, but we need this URI when we login internally, see net.py
        "register": Uri("/accounts/register/"),
        "activate": Uri("/accounts/activate/"),
        "profile": Uri("/accounts/profile/"),
    },
    "homepage": {
        "view": Uri("http://mini-buildd.installiert.net/"),
    },
}

MAIN_MENU_LEFT = ["events", "builds", "repositories", "|", "manual", "api"]
MAIN_MENU_RIGHT = ["builders", "crontab", "log", "|", "setup"]


class Route:
    """Link a path (may be None, may be run-time) to uris (may be empty)."""

    def __init__(self, path=None, uris=None):
        self.path = path
        self.uris = {} if uris is None else uris

    def static_uri(self, path=""):
        uri = self.uris.get("static")
        return uri.join(path) if uri else None


class Routes(dict):
    def _add(self, name, path):
        self[name] = Route(path, uris=URIS.get(name))

    def __init__(self, home):
        super().__init__()
        self.home = home

        def homepth(path):
            return os.path.join(self.home, path)

        self._add("home", mini_buildd.files.Path(self.home))
        self._add("setup", None)
        self._add("static", mini_buildd.files.Path(f"{PY_PACKAGE_PATH}/mini_buildd/static"))

        # 'var/shared': Shared with chroots
        self._add("builds", mini_buildd.files.Path(homepth("var/shared/builds"), create=True))   # Build directory
        self._add("libdir", mini_buildd.files.Path(homepth("var/shared/libdir"), create=True))   # Shared libdir: Can optionally be used for persistent shared data (like ccache)

        self._add("events", mini_buildd.files.Path(homepth("var/events"), create=True))
        self._add("event_hooks", mini_buildd.files.Path(homepth("etc/event-hooks"), create=True))

        self._add("repositories", mini_buildd.files.Path(homepth("repositories"), create=True))
        self._add("chroots", mini_buildd.files.Path(homepth("var/chroots"), create=True))
        self._add("builders", None)
        self._add("crontab", mini_buildd.files.Path(homepth("var/crontab"), create=True))
        self._add("sitemap", None)
        self._add("api", None)

        self._add("log", mini_buildd.files.Path(homepth("var/log"), create=True))

        self._add("tmp", mini_buildd.files.Path(homepth("var/tmp"), create=True))
        self._add("incoming", mini_buildd.files.Path(homepth("incoming"), create=True))

        self._add("manual", mini_buildd.files.Path(os.path.realpath("/usr/share/doc/mini-buildd/html")))


#: Variable items (see mini-buildd main script)
DEBUG = []
FOREGROUND = False
HOSTNAME = socket.gethostname()
HOSTNAME_FQDN = socket.getfqdn()
#: HTTP endpoints strings
HTTP_ENDPOINTS = ["tcp6:port=8066"]
ROUTES = {}


def default_ftp_endpoint():
    """Compute default ftp endpoint string from main http endpoint."""
    port = mini_buildd.http_endpoint().options.get("port")
    return mini_buildd.http_endpoint().description.replace(f"port={port}", f"port={int(port)+1}")


def default_identity():
    return HOSTNAME


def python37_systemcert_workaround():
    """
    .. attention:: **compat** (``python < 3.8``): ``SSL_CERT_FILE`` workaround.

      - python 3.7/buster just does not use the system's default cert store, although documented as such.
      - python 3.8/bullseye seems to work just fine.
      - This seems to be due to openssl's set_default_verify_paths() (or maybe how it's called in py 3.7).
      - Explicitly setting env "SSL_CERT_FILE" seems to fix openssl
      - https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=805646 (may-be the issue).

      I guess openssl is so secure just because it does not work 99% of the time :).
    """
    if sys.version_info < (3, 8):
        os.environ["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-certificates.crt"


python37_systemcert_workaround()
