from datetime import datetime
from typing import Callable

from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.core.exceptions import ValidationError
from django.db import models
from django.utils.translation import gettext_lazy as _

from scheduler import tools

ARG_TYPE_TYPES_DICT = {
    'str': str,
    'int': int,
    'bool': bool,
    'datetime': datetime,
    'callable': Callable,
}


class BaseTaskArg(models.Model):
    class ArgType(models.TextChoices):
        STR = 'str', _('string')
        INT = 'int', _('int')
        BOOL = 'bool', _('boolean')
        DATETIME = 'datetime', _('datetime')
        CALLABLE = 'callable', _('callable')

    arg_type = models.CharField(
        _('Argument Type'), max_length=12, choices=ArgType.choices, default=ArgType.STR,
    )
    val = models.CharField(_('Argument Value'), blank=True, max_length=255)
    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
    object_id = models.PositiveIntegerField()
    content_object = GenericForeignKey()

    def clean(self):
        if self.arg_type not in ARG_TYPE_TYPES_DICT:
            raise ValidationError({
                'arg_type': ValidationError(
                    _(f'Could not parse {self.arg_type}, options are: {ARG_TYPE_TYPES_DICT.keys()}'), code='invalid')
            })
        try:
            if self.arg_type == 'callable':
                tools.callable_func(self.val)
            elif self.arg_type == 'datetime':
                datetime.fromisoformat(self.val)
            elif self.arg_type == 'bool':
                if self.val.lower() not in {'true', 'false'}:
                    raise ValidationError
            elif self.arg_type == 'int':
                int(self.val)
        except Exception:
            raise ValidationError({
                'arg_type': ValidationError(
                    _(f'Could not parse {self.val} as {self.arg_type}'), code='invalid')
            })

    def save(self, **kwargs):
        super(BaseTaskArg, self).save(**kwargs)
        self.content_object.save()

    def delete(self, **kwargs):
        super(BaseTaskArg, self).delete(**kwargs)
        self.content_object.save()

    def value(self):
        if self.arg_type == 'callable':
            res = tools.callable_func(self.val)()
        elif self.arg_type == 'datetime':
            res = datetime.fromisoformat(self.val)
        elif self.arg_type == 'bool':
            res = self.val.lower() == 'true'
        else:
            res = ARG_TYPE_TYPES_DICT[self.arg_type](self.val)
        return res

    class Meta:
        abstract = True
        ordering = ['id']


class TaskArg(BaseTaskArg):
    def __str__(self):
        return f'TaskArg[arg_type={self.arg_type},value={self.value()}]'


class TaskKwarg(BaseTaskArg):
    key = models.CharField(max_length=255)

    def __str__(self):
        key, value = self.value()
        return f'TaskKwarg[key={key},arg_type={self.arg_type},value={self.val}]'

    def value(self):
        return self.key, super(TaskKwarg, self).value()
