import logging

from django.conf import settings
from django.contrib.postgres.fields import ArrayField
from django.db import connection
from django.db.models import Model, BooleanField, BigAutoField, DateTimeField, Transform, CharField, TextField, BigIntegerField
from django.utils import timezone
from isc_common import setAttr, delAttr
from isc_common.fields.code_field import CodeField
from isc_common.managers.common_manager import CommonManager, CommonQuerySet
from isc_common.number import GetListFromStringCortege
from transliterate import translit
from transliterate.exceptions import LanguageDetectionError

logger = logging.getLogger(__name__)


class Model_withOldId(Model):
    old_id = BigIntegerField(db_index=True, null=True, blank=True, unique=True)

    class Meta:
        abstract = True


class Model_withOldIdStr(Model):
    old_id = CodeField(unique=True)

    class Meta:
        abstract = True


class Model_withOldIds(Model):
    old_ids = ArrayField(BigIntegerField(), null=True, blank=True, unique=True)

    class Meta:
        abstract = True


class AuditQuerySet(CommonQuerySet):
    def getOptional(self, *args, **kwargs):
        try:
            return self.get(*args, **kwargs)
        except self.model.DoesNotExist:
            return None

    def _check_ed_izm_qty(self, **kwargs):
        if kwargs.get('ed_izm') is not None and kwargs.get('qty') is None or kwargs.get('ed_izm') is None and kwargs.get('qty') is not None:
            raise Exception(f'Единица измерения не может быть указана без количества')

    def __init__(self, model=None, query=None, using=None, hints=None, alive_only=True):
        self.alive_only = alive_only
        super().__init__(model=model, query=query, using=using, hints=hints, alive_only=alive_only)

    def rearrange_parent(self, json):
        # _criteria = json.get('data').get('criteria')
        # if isinstance(_criteria, list):
        #     for criterion in _criteria:
        #         if criterion.get('fieldName') == 'parent_id':
        #             item_id = criterion.get('value')
        #             if (item_id, int):
        #                 criteria_lst = [criterion for criterion in _criteria if criterion.get('fieldName') == 'parent_id' and criterion.get('value') is not None]
        #                 if len(criteria_lst) > 0:
        #                     setAttr(json.get('data'), 'criteria', criteria_lst)
        return json

    def soft_delete(self):
        res = super().update(deleted_at=timezone.now())
        if res > 0:
            return self
        else:
            return None

    def soft_restore(self):
        res = super().update(deleted_at=None)
        return res

    def hard_delete(self):
        res = super().delete()
        return res

    def alive(self):
        return self.filter(deleted_at=None)

    def dead(self):
        return self.exclude(deleted_at=None)

    def update(self, **kwargs):
        if kwargs.get('lastmodified') is None:
            setAttr(kwargs, 'lastmodified', timezone.now())
        return super().update(**kwargs)


class AuditManager(CommonManager):
    @classmethod
    def getRecord(cls, record):
        return dict()

    def __init__(self, *args, **kwargs):
        self.alive_only = kwargs.pop('alive_only', True)
        super().__init__(*args, **kwargs)

    def get_queryset(self):
        if self.alive_only:
            return AuditQuerySet(model=self.model, alive_only=self.alive_only).filter(deleted_at=None)
        return AuditQuerySet(model=self.model, alive_only=self.alive_only)

    def hard_delete(self):
        return self.get_queryset().hard_delete()

    def soft_delete(self):
        return self.get_queryset().soft_delete()

    def soft_restore(self):
        return self.get_queryset().soft_restore()

    def getOptional(self, *args, **kwargs):
        try:
            return self.get(*args, **kwargs)
        except self.model.DoesNotExist:
            return None

    def getOr(self, *args, **kwargs):
        alternative = kwargs.get('alternative')
        try:
            delAttr(kwargs, 'alternative')
            return self.get(*args, **kwargs)
        except self.model.DoesNotExist:
            if not callable(alternative):
                raise Exception('alternative must be callable')
            return alternative()
        except self.model.MultipleObjectsReturned as ex:
            old_ids = kwargs.get('old_ids__overlap')
            old_id = kwargs.get('old_id')
            if old_ids is None and old_id is None:
                if not callable(alternative):
                    raise Exception('alternative must be callable')
                return alternative()

            if len(old_ids) > 1:
                raise ex

            old_id = old_ids[0]

            for item in self.filter(*args, **kwargs):
                if len(item.old_ids) > 1:
                    item.old_ids = list(filter(lambda x: x != old_id, item.old_ids))
                    item.save()

            return self.get(*args, **kwargs)

    def get_record(self, record):
        record = self.get(id=record.id if isinstance(record, Model) else record.get('id'))
        self.getRecord(record=record)


class Manager(CommonManager):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def get_queryset(self):
        return AuditQuerySet(model=self.model)


class AuditModel(Model):
    id = BigAutoField(primary_key=True, verbose_name="Идентификатор")
    deleted_at = DateTimeField(verbose_name="Дата мягкого удаления", null=True, blank=True, db_index=True)
    editing = BooleanField(verbose_name="Возможность редактирования", default=True)
    deliting = BooleanField(verbose_name="Возможность удаления", default=True)
    lastmodified = DateTimeField(verbose_name='Последнее обновление', editable=False, db_index=True, default=timezone.now)

    @classmethod
    def get_urls_datas(cls, record, keyimages, model, model_images, main_model, imports, django_model, django_model_images, code=None, add_params=[]):
        from isc_common.number import DelProps

        if not isinstance(record, dict):
            raise Exception('Record must be dict.')

        record = DelProps(record)
        res = list()
        idx = 0
        for keyimage in keyimages:
            real_name, image_src = cls.get_urls_data(
                add_params=add_params[idx] if len(add_params) != 0 else '',
                code=code,
                django_model=django_model,
                django_model_images=django_model_images,
                id=record.get('id'),
                imports=imports,
                keyimage=keyimage,
                main_model=main_model,
                model=model,
                model_images=model_images,
            )
            idx += 1

            real_name, image_id = GetListFromStringCortege(real_name)
            image_src = f'{settings.IMAGE_CONTENT_PROTOCOL}://{settings.IMAGE_CONTENT_HOST}:{settings.IMAGE_CONTENT_PORT}/{image_src}'
            item = {
                f'{keyimage}_image_id': image_id,
                f'{keyimage}_image_src': image_src,
                f'{keyimage}_real_name': real_name,
            }
            res.append(item)

        for urls_data in res:
            for k, v in urls_data.items():
                setAttr(record, k, v)
        return record

    @classmethod
    def get_urls_data(cls, id, keyimage, model, model_images, main_model, imports, django_model, django_model_images, code=None, add_params=''):
        if code is None:
            code = main_model

        if add_params:
            add_params = f'&{add_params}'

        _imports = ','.join(imports)

        with connection.cursor() as cursor:
            sql_str = f'''select (select (ici.real_name, usi.image_id)
                                        from {model_images} as usi
                                                 join isc_common_images ici on ici.id = usi.image_id
                                                 join isc_common_image_types icit on usi.type_id = icit.id
                                        where usi.main_model_id = cd.id
                                          and icit.code = %s
                                          and icit.keyimage = %s
                                          and usi.deleted_at is null
                                        limit 1)                                                                                                           as real_name,
                                       concat('logic/Imgs/Download/', (
                                           select usi.image_id
                                           from {model_images} as usi
                                                    join isc_common_images ici on ici.id = usi.image_id
                                                    join isc_common_image_types icit on usi.type_id = icit.id
                                           where usi.main_model_id = cd.id
                                             and icit.code = %s
                                             and icit.keyimage = %s
                                             and usi.deleted_at is null
                                           limit 1), '?', 
                                           'code={code}', 
                                           '&', 
                                           'keyimage={keyimage}', 
                                           '&', 
                                           'path={main_model}', 
                                           '&', 
                                           'main_model={main_model}', 
                                           '&', 
                                           'main_model_id=', cd.id, 
                                           '&', 
                                           'imports=', '{_imports}', 
                                           '&', 
                                           'django_model=', '{django_model._meta.object_name}',
                                           '&', 
                                           'django_model_images=', '{django_model_images._meta.object_name}',
                                           '{add_params}'
                                           ) as image_src
                                from {model} as cd
                                where cd.id = %s'''

            cursor.execute(sql_str, [code, keyimage, code, keyimage, id])
            record = cursor.fetchone()
            return record

    @classmethod
    def uncapitalize(cls, str):
        from isc_common.common.functions import uncapitalize
        return uncapitalize(str)

    @classmethod
    def translit(cls, value):
        if isinstance(value, int):
            return str(value)
        try:
            return cls.uncapitalize(translit(value, reversed=True).replace("'", '').replace(' ', '_').replace('.', '_'))
        except LanguageDetectionError:
            return cls.uncapitalize(value.replace("'", '').replace(' ', '_').replace('.', '_'))

    @classmethod
    def dbl_qutes_str(cls, str):
        from isc_common.common.functions import dbl_qutes_str
        return dbl_qutes_str(str)

    @classmethod
    def qutes_str(cls, str):
        from isc_common.common.functions import qutes_str
        return qutes_str(str)

    @property
    def idHidden(self):
        return not self is None

    @property
    def is_deleted(self):
        return self.deleted_at is not None

    class Meta:
        abstract = True

    def soft_delete(self):
        self.deleted_at = timezone.now()
        self.save()
        return self

    def soft_restore(self):
        self.deleted_at = None
        self.save()
        return self

    objects = AuditManager()
    all_objects = AuditManager(alive_only=False)


class Dbl_spacesValue(Transform):
    lookup_name = 'delete_dbl_spaces'
    function = 'delete_dbl_spaces'


CharField.register_lookup(Dbl_spacesValue)
TextField.register_lookup(Dbl_spacesValue)


class Trim_Value(Transform):
    lookup_name = 'trim'
    function = 'trim'


CharField.register_lookup(Trim_Value)
TextField.register_lookup(Trim_Value)


class Upper_Value(Transform):
    lookup_name = 'upper'
    function = 'upper'


CharField.register_lookup(Upper_Value)
TextField.register_lookup(Upper_Value)
