import logging
from copy import deepcopy
from unittest import mock

from django.conf import settings
from django.core.management import call_command
from django.core.management.base import BaseCommand
from django.db import OperationalError, transaction
from django.template import base as template
from django.template import defaulttags
from django.utils.translation import override

from wagtail.contrib.settings.templatetags import wagtailsettings_tags
from wagtail.images.templatetags import wagtailimages_tags

from oscar.core.loading import get_class, get_model

from ocyan.core.fender import config
from ocyan.main.templatetags import ocyan_main
from ocyan.plugin.oscar.info import OSCAR_NEEDS_OXYAN

logger = logging.getLogger(__name__)

if OSCAR_NEEDS_OXYAN:
    CommunicationEventType = get_model("communication", "CommunicationEventType")
else:
    CommunicationEventType = get_model("customer", "CommunicationEventType")

EmailContext = get_class("order.context", "EmailContext")


# override forloop registration
def do_not_change_anything(parser, token):  # pylint: disable=unused-argument
    return template.TextNode("{%s}" % "% {} %".format(token.contents))


def execute_and_rerender(func):
    def _wrapped(parser, token):
        func(parser, token)
        return do_not_change_anything(parser, token)

    return _wrapped


# override filters, leave them unchanged
class NoFilterExpression(template.FilterExpression):
    def resolve(self, context, ignore_failures=False):
        if "|" in self.token:
            return "{{ %s }}" % self.token
        return super().resolve(context)


# override parse so filters will not be changed
class NoFilterParser(template.Parser):
    def compile_filter(self, token):
        return NoFilterExpression(token, self)


# make sure variables are rendered unchanged.
class DotPath(object):
    def __init__(self, *names):
        self.names = names

    def __getitem__(self, name):
        return DotPath(*self.names, name)

    def __str__(self):
        return "{{ %s }}" % ".".join(self.names)


class Command(BaseCommand):
    help = "Populate the email communications in the dashboard"

    def add_arguments(self, parser):
        parser.add_argument(
            "event_types", nargs="+", help="CommunicationEventType code names"
        )
        parser.add_argument(
            "--language-code",
            "-l",
            default=settings.LANGUAGE_CODE,
            help="language code to activate",
        )
        parser.add_argument(
            "--variable",
            action="append",
            type=str,
            help="additional context variables used in the template",
        )
        parser.add_argument(
            "--dry-run",
            default=False,
            action="store_true",
            help="Do not really save the CommunicationEventTypes",
        )
        parser.add_argument(
            "--show-html",
            default=False,
            action="store_true",
            help="Show html template also",
        )
        parser.add_argument(
            "--is-order-email",
            default=False,
            action="store_true",
            help="Use if the communicationevent is not user but order related",
        )

    def get_messages(self, code, options):
        ctx = {
            "user": DotPath("user"),
            "site": DotPath("site"),
            "logo": DotPath("logo"),
            "order": DotPath("order"),
            "status_url": DotPath("status_url"),
            "static_base_url": DotPath("static_base_url"),
            "tracking_url": DotPath("tracking_url"),
            "line": DotPath("line"),
            "field": DotPath("field"),
            "reset_url": DotPath("reset_url"),
            "new_email": DotPath("new_email"),
            "alert": DotPath("alert"),
            "lines": DotPath("lines"),
            "quantity": DotPath("quantity"),
            "option": DotPath("option"),
            "discount": DotPath("discount"),
            "charge": DotPath("charge"),
            "settings": DotPath("settings"),
            "request": DotPath("request"),
        }
        extra_context = options["variable"] or []

        for var in extra_context:
            ctx[var] = DotPath(var)

        # add EmailContext vaiables to context
        # this is casted back to dict, because if varables
        # in the ctx are DotPath, we don;t want some property
        # on EmailContext get priority, which could erase the
        # DotPath in ctx.
        email_ctx = dict(EmailContext(**ctx))
        messages = CommunicationEventType(code=code).get_messages(email_ctx)

        if options["is_order_email"]:
            messages["body"] = "{% load currency_filters %}\n" + messages["body"]
            if messages["html"]:
                messages["html"] = "{% load currency_filters %}\n" + messages["html"]
            else:
                messages["html"] = None

        return messages

    def handle(self, *args, **options):
        try:
            with transaction.atomic():
                try:
                    self._handle(**options)
                except ValueError:
                    # it failed because collectstatic made a booboo
                    call_command("collectstatic", "--noinput")
                    self._handle(**options)
        except (AttributeError, OperationalError):
            logger.warning("Unable to populate dashboard emails at this time")

    def _handle(self, **options):
        # make a copy of the tags for that can be changed and used in this command
        default_tags_copy = deepcopy(defaulttags.register.tags)
        wagtailimages_tags_copy = deepcopy(wagtailimages_tags.register.tags)
        wagtailsettings_tags_copy = deepcopy(wagtailsettings_tags.register.tags)
        ocyan_main_copy = deepcopy(ocyan_main.register.tags)

        with (
            mock.patch(
                "compressor.base.Compressor.template_name_inline",
                "override_compressor/css_inline.html",
                create=True,
            ),
            mock.patch.object(ocyan_main.register, "tags", ocyan_main_copy),
            mock.patch.object(
                wagtailsettings_tags.register, "tags", wagtailsettings_tags_copy
            ),
            mock.patch.object(
                wagtailimages_tags.register, "tags", wagtailimages_tags_copy
            ),
            mock.patch.object(defaulttags.register, "tags", default_tags_copy),
            mock.patch.object(template, "Parser", NoFilterParser),
        ):
            defaulttags.register.tag("load", execute_and_rerender(defaulttags.load))
            defaulttags.register.tag("if", do_not_change_anything)
            defaulttags.register.tag("else", do_not_change_anything)
            defaulttags.register.tag("elif", do_not_change_anything)
            defaulttags.register.tag("endif", do_not_change_anything)

            defaulttags.register.tag("for", do_not_change_anything)
            defaulttags.register.tag("endfor", do_not_change_anything)
            wagtailimages_tags.register.tag("image", do_not_change_anything)
            wagtailsettings_tags.register.tag("get_settings", do_not_change_anything)
            ocyan_main.register.tag("absolute_url", do_not_change_anything)

            with override(options["language_code"]):
                for code in options["event_types"]:
                    if (
                        CommunicationEventType.objects.filter(code=code).exists()
                        and not options["dry_run"]
                    ):
                        self.stdout.write(
                            self.style.WARNING("%s allready exists, skipping." % code)
                        )
                    else:
                        messages = self.get_messages(code, options)

                        if not messages["subject"] or not messages["body"]:
                            self.stdout.write(
                                self.style.ERROR(
                                    "skipping %s, no template found" % code
                                )
                            )
                            continue

                        if options["dry_run"]:
                            self.stdout.write(self.style.SUCCESS("subject:"))
                            self.stdout.write("%(subject)s\n\n" % messages)
                            self.stdout.write(self.style.SUCCESS("body:"))
                            self.stdout.write("%(body)s" % messages)
                            if options["show_html"]:
                                self.stdout.write(self.style.SUCCESS("html"))
                                self.stdout.write("%(html)s:" % messages)
                        else:
                            if options["is_order_email"]:
                                category = CommunicationEventType.ORDER_RELATED
                            else:
                                category = CommunicationEventType.USER_RELATED

                            evt = CommunicationEventType.objects.create(
                                code=code,
                                name=code,
                                category=category,
                                email_subject_template=messages["subject"],
                                email_body_template=messages["body"],
                                email_body_html_template=messages["html"],
                            )
                            if config.i18n_enabled:
                                for language_code, _ in settings.LANGUAGES:
                                    if language_code != options["language_code"]:
                                        with override(language_code):
                                            messages = self.get_messages(code, options)
                                            evt.email_subject_template = messages[
                                                "subject"
                                            ]
                                            evt.email_body_template = messages["body"]
                                            evt.email_body_html_template = messages[
                                                "html"
                                            ]
                                            evt.save()

        self.stdout.write(
            self.style.SUCCESS(
                "populating emails for events: %(event_types)s in language %(language_code)s done."
                % options
            )
        )
