from abc import ABC, abstractmethod
from datetime import timedelta

from django.core.exceptions import ValidationError
from django.utils.text import camel_case_to_spaces
from django.utils.translation import gettext
from django.utils.translation import gettext_lazy as _

from .choices import COMPLETED_TASK_STATUSUS


class AbstractAction(ABC):
    name = None
    code = None
    retry_after = timedelta(minutes=10)
    is_highvolume = False

    def __init__(self, data=None, *, name=None, code=None):
        self.data = data if data is not None else {}
        if name is not None:
            self.name = name
        if code is not None:
            self.code = code

    @classmethod
    def deserialize(cls, task):
        return cls(task.data, name=task.name, code=task.code)

    @abstractmethod
    def get_name(self):
        pass

    @abstractmethod
    def run(self, log):
        pass

    def validate_task(self, task):
        pass

    def get_code(self):
        return self.code

    def get_data(self):
        return self.data

    def get_retry_after(self):
        return self.retry_after

    def get_is_highvolume(self):
        return self.is_highvolume

    @classmethod
    def get_importstring(cls):
        """This method should return the location of the action so that it can be stored in the task"""
        return "%s.%s" % (cls.__module__, cls.__name__)

    def __str__(self):
        return "ocyan.task.%s: %s %s" % (
            self.__class__.__name__,
            self.get_importstring(),
            self.data,
        )


class Action(AbstractAction):
    name_pattern = "%(verbose_name)s %(name)s"

    def __init__(self, data=None, *, name=None, code=None, retry_after=None):
        """In the initializer we need to save the data on the 'Action' instance"""
        super().__init__(data, name=name, code=code)
        if retry_after is not None:
            self.retry_after = retry_after

    def get_name(self):
        return self.name_pattern % dict(
            verbose_name=gettext(
                camel_case_to_spaces(self.__class__.__name__).capitalize()
            ),
            code=self.get_code(),
            retry_after=self.get_retry_after(),
            name=self.name or "",
            **self.data,
        )


class ExclusiveAction(Action):
    name_pattern = "%(verbose_name)s %(name)s %(code)s"

    def validate_task(self, task):
        code = task.code
        if not code:
            raise ValidationError(
                _(
                    "Exclusive actions are exclusive per code, %s.get_code should provide a non empty code"
                )
                % self.get_importstring()
            )

        Task = task._meta.model
        if (
            Task.objects.filter(code=code)
            .exclude(status__in=COMPLETED_TASK_STATUSUS)
            .exists()
        ):
            raise ValidationError(
                _(
                    "There may only be 1 of this task at a time. A second one may not be created"
                )
            )


class UniqueAction(ExclusiveAction):
    def validate_task(self, task):
        code = task.code
        if code is None:
            raise ValidationError(
                gettext("Unique actions require a (unique) task code")
            )

        Task = task._meta.model
        if Task.objects.filter(code=code).exists():
            raise ValidationError(
                gettext("A Unique Action with code %s allready exist.") % code
            )
