import contextlib
import os
import unicodedata

from django.conf import settings
from django.db import transaction
from django.template.defaultfilters import date as _date
from django.utils.crypto import get_random_string
from django.utils.translation import activate, get_language
from django.utils.translation import gettext_lazy as _
from i18nfield.strings import LazyI18nString
from i18nfield.utils import I18nJSONEncoder


def daterange_de(date_from, date_to):
    if (
        date_from.year == date_to.year
        and date_from.month == date_to.month
        and date_from.day == date_to.day
    ):
        return str(_date(date_from, "j. F Y"))
    if date_from.year == date_to.year and date_from.month == date_to.month:
        return "{}.–{}".format(_date(date_from, "j"), _date(date_to, "j. F Y"))
    if date_from.year == date_to.year:
        return "{} – {}".format(_date(date_from, "j. F"), _date(date_to, "j. F Y"))
    return ""


def daterange_en(date_from, date_to):
    if (
        date_from.year == date_to.year
        and date_from.month == date_to.month
        and date_from.day == date_to.day
    ):
        return str(_date(date_from, "N jS, Y"))
    if date_from.year == date_to.year and date_from.month == date_to.month:
        return "{} – {}".format(_date(date_from, "N jS"), _date(date_to, "jS, Y"))
    if date_from.year == date_to.year:
        return "{} – {}".format(_date(date_from, "N jS"), _date(date_to, "N jS, Y"))
    return ""


def daterange_es(date_from, date_to):
    if (
        date_from.year == date_to.year
        and date_from.month == date_to.month
        and date_from.day == date_to.day
    ):
        return "{}".format(_date(date_from, "DATE_FORMAT"))
    if date_from.year == date_to.year and date_from.month == date_to.month:
        return "{} - {} de {} de {}".format(
            _date(date_from, "j"),
            _date(date_to, "j"),
            _date(date_to, "F"),
            _date(date_to, "Y"),
        )
    if date_from.year == date_to.year:
        return "{} de {} - {} de {} de {}".format(
            _date(date_from, "j"),
            _date(date_from, "F"),
            _date(date_to, "j"),
            _date(date_to, "F"),
            _date(date_to, "Y"),
        )
    return ""


def daterange(date_from, date_to):
    language = get_language()[:2]
    lookup = {
        "de": daterange_de,
        "en": daterange_en,
        "es": daterange_es,
    }
    function = lookup.get(language)
    result = function(date_from, date_to) if function else None
    return result or _("{date_from} – {date_to}").format(
        date_from=_date(date_from, "DATE_FORMAT"), date_to=_date(date_to, "DATE_FORMAT")
    )


class I18nStrJSONEncoder(I18nJSONEncoder):
    def default(self, obj):
        if isinstance(obj, LazyI18nString):
            return str(obj)
        return super().default(obj)


def path_with_hash(name):
    dir_name, file_name = os.path.split(name)
    file_root, file_ext = os.path.splitext(file_name)
    random = get_random_string(7)
    return os.path.join(dir_name, f"{file_root}_{random}{file_ext}")


@contextlib.contextmanager
def rolledback_transaction():
    """This context manager runs your code in a database transaction that will
    be rolled back in the end.

    This can come in handy to simulate the effects of a database
    operation that you do not actually want to perform. Note that
    rollbacks are a very slow operation on most database backends. Also,
    long-running transactions can slow down other operations currently
    running and you should not use this in a place that is called
    frequently.
    """

    class DummyRollbackException(Exception):
        pass

    try:
        with transaction.atomic():
            yield
            raise DummyRollbackException()
    except DummyRollbackException:
        pass
    else:  # pragma: no cover
        raise Exception("Invalid state, should have rolled back.")


@contextlib.contextmanager
def language(language_code):
    previous_language = get_language()
    activate(language_code or settings.LANGUAGE_CODE)
    try:
        yield
    finally:
        activate(previous_language)


def safe_filename(filename):
    return unicodedata.normalize("NFD", filename).encode("ASCII", "ignore").decode()
