import logging
import http
import shlex
import os.path
import traceback
import ssl

import django.core.exceptions

import mini_buildd.version
import mini_buildd.config
import mini_buildd.net

LOG = logging.getLogger(__name__)

__version__ = mini_buildd.version.__version__


#: For use in fstrings
NEWLINE = "\n"


def fopen(path, mode="r", **kwargs):
    """
    Text file open with our fixed char encoding (UTF-8).

    UTF-8 may become default for open at some point, but not just yet.

    See https://www.python.org/dev/peps/pep-0597/
    """
    return open(path, mode, encoding=mini_buildd.config.CHAR_ENCODING, **kwargs)


class PyCompat:
    """Misc helpers to stay compatible with python <= 3.6 (see ``debian/control``)."""

    @staticmethod
    def removeprefix(s, prefix):
        """
        .. attention:: **compat** (``python < 3.9``): ``str.removeprefix``.
        """
        if s.startswith(prefix):
            return s[len(prefix):]
        return s

    @staticmethod
    def shlex_join(split_command):
        """
        .. attention:: **compat** (``python < 3.8``): ``shlex.join``.
        """
        return " ".join(shlex.quote(arg) for arg in split_command)


def http_endpoint(number=0):
    return mini_buildd.net.ServerEndpoint(mini_buildd.config.HTTP_ENDPOINTS[number], protocol="http")


def get_daemon():
    """Shortcut to access daemon singleton."""
    from mini_buildd.daemon import Daemon
    return Daemon()


def mdls():
    """
    Get python package ``mini_buildd.models`` with all needed modules available (sort-of dependency injection).

    Code may just use this w/o the need to import django-related
    code, neither as proper main import (this usually fails as
    django needs to be set up first) nor as in-code import (this
    is dirty and also usually needs a static code checker
    exemption).
    """
    return mini_buildd.models


class Rfc7807:
    def __init__(self, status, detail=None):
        self.status = status if isinstance(status, http.HTTPStatus) else http.HTTPStatus(status)
        self.detail = f"No public details (see <a href='{mini_buildd.config.URIS['log']['view'].uri}daemon.log'>daemon.log</a>)" if detail is None else detail

    def __str__(self):
        return f"{self.detail} (HTTP {self.status.value} {self.status.description})"

    def to_json(self):
        return {"type": "about:blank",  # No special semantics. Status code is HTTP status code, title is HTTP status message
                "status": self.status.value,
                "title": self.status.description,
                "detail": self.detail,
                "instance": "about:blank"}

    @classmethod
    def from_json(cls, json):
        return Rfc7807(json["status"], json["detail"])


class HTTPError(Exception):
    """Public (HTTP) exception -- raise this if the exception string is ok for user consumption."""

    def __init__(self, status, detail=None):
        self.rfc7807 = Rfc7807(status, detail)
        self.status = self.rfc7807.status.value
        self.detail = self.rfc7807.detail
        super().__init__(str(self.rfc7807))

    def __str__(self):
        return str(self.rfc7807)


class HTTPOk(HTTPError):
    def __init__(self, detail=None):
        super().__init__(http.HTTPStatus.OK, detail)


class HTTPNotFound(HTTPError):
    def __init__(self, detail=None):
        super().__init__(http.HTTPStatus.NOT_FOUND, detail)


class HTTPBadRequest(HTTPError):
    def __init__(self, detail=None):
        super().__init__(http.HTTPStatus.BAD_REQUEST, detail)


class HTTPUnauthorized(HTTPError):
    def __init__(self, detail=None):
        super().__init__(http.HTTPStatus.UNAUTHORIZED, detail)


class HTTPUnavailable(HTTPError):
    def __init__(self, detail=None):
        super().__init__(http.HTTPStatus.SERVICE_UNAVAILABLE, detail)


HTTPShutdown = HTTPUnavailable("Server Shutdown")


class HTTPInternal(HTTPError):
    def __init__(self, detail=None):
        super().__init__(http.HTTPStatus.INTERNAL_SERVER_ERROR, detail)


def log_exception(log, message, exception, level=logging.WARNING):
    log.log(level, f"{message}: {exception}", exc_info="exception" in mini_buildd.config.DEBUG)


def log_stack(log, level=logging.INFO):
    """Log stack (for debugging)"""
    log.log(level, "".join(traceback.format_stack()))


def e2http(exception, status=http.HTTPStatus.INTERNAL_SERVER_ERROR):
    if isinstance(exception, django.core.exceptions.ValidationError):
        return mini_buildd.HTTPBadRequest(" ".join(exception.messages))
    return exception if isinstance(exception, mini_buildd.HTTPError) else mini_buildd.HTTPError(status)


def rrpes(func, *args, **kwargs):
    """Run ``func``. On exception, return public error str."""
    try:
        return func(*args, **kwargs)
    except BaseException as e:
        mini_buildd.log_exception(LOG, f"'{func}' failed (error str returned)", e, level=logging.INFO)
        return str(mini_buildd.e2http(e))


def check_program(path, deb=None):
    if not os.path.exists(path):
        raise HTTPUnavailable(f"Program not found: {path} (please install Debian package {deb})")


def systemcert_workaround():
    """
    Set environment ``SSL_CERT_FILE`` to point to system's certificate store (ca-certificates) when not using ``libssl3``

    .. tip:: **compat** (``< libssl3``): ``python`` fails for (self-signed) certificate even though it's enrolled with system's ``ca-certificates``?

        You will get some error like ``certificate verify failed: self signed certificate``.

        This seems to occur with ``libssl1``, while it seems fine with ``libssl3`` (even though :debbug:`805646` never got closed).

        Check the package deps on ``libpythonX.Y-minimal`` to see if it was build against ``libssl1`` or ``libssl3`` (or mini-buildd's log for warning message).

        Simplest workaround is to set ``SSL_CERT_FILE`` environment, for example like so:

        .. code:: bash

          export SSL_CERT_FILE="/etc/ssl/certs/ca-certificates.crt"

    """
    if ssl.OPENSSL_VERSION_INFO[0] < 3:
        os.environ["SSL_CERT_FILE"] = "/etc/ssl/certs/ca-certificates.crt"
        LOG.warning("Setting environment SSL_CERT_FILE='/etc/ssl/certs/ca-certificates.crt' for openssl < 3 (use system's cert store)")
