from functools import reduce

from django.conf import settings
from django.db import models
from rest_framework.fields import empty, Field

from isapilib.core.exceptions import AbortCreationException
from isapilib.external.utilities import get_param_sucursal, get_param_empresa


class JsonField(Field):
    def __init__(self, json_path=None, return_type=None, prefix=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.json_path = json_path
        self.return_type = return_type or str
        self.prefix = prefix

    def _get_json_value(self, dictionary):
        real_json_path = self.json_path or self.field_name

        try:
            return reduce(lambda d, key: d[key], real_json_path.split("."), dictionary)
        except (KeyError, TypeError):
            return empty

    def get_value(self, dictionary):
        value = self._get_json_value(dictionary)
        if self.prefix:
            value = f'{self.prefix}{value}'
        return value

    def to_internal_value(self, data):
        return self.return_type(data)

    def to_representation(self, value):
        return self.return_type(value)


class SerializerSetMethodField(Field):
    def __init__(self, method_name_get=None, method_name_set=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.method_name_get = method_name_get
        self.method_name_set = method_name_set
        self._value = empty

    def bind(self, field_name, parent):
        super().bind(field_name, parent)
        if self.method_name_get is None: self.method_name_get = f'get_{field_name}'
        if self.method_name_set is None: self.method_name_set = f'set_{field_name}'

    def get_value(self, dictionary):
        if self._value is not empty: return self._value
        method = getattr(self.parent, self.method_name_set)
        self._value = method(dictionary)
        return self._value

    def to_internal_value(self, data):
        return data

    def to_representation(self, value):
        if isinstance(value, (str, int, float)):
            return value
        if issubclass(type(value), models.Model):
            return value.pk
        return None


class GetOrField(JsonField):
    def __init__(self, model, model_field=None, related=True, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.model = model
        self.model_field = model_field
        self.related = related

    def bind(self, field_name, parent):
        super().bind(field_name, parent)
        if self.model_field is None: self.model_field = field_name

    def to_internal_value(self, data):
        instance = self.model.objects.filter(**{self.model_field: data}).first()
        if instance is None: return empty
        return instance if self.related else instance.pk

    def to_representation(self, value):
        return getattr(value, 'pk', value)


class GetOrDefaultField(GetOrField):
    def __init__(self, sucursal_param=None, empresa_param=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.sucursal_param = sucursal_param
        self.empresa_param = empresa_param

    def get_value(self, dictionary):
        value = super().get_value(dictionary)
        if value is not empty: return value

        pk = empty
        if self.sucursal_param:
            instance = self.parent.get_instance()
            pk = get_param_sucursal(instance.sucursal, self.sucursal_param)

        if self.empresa_param:
            interfaz = getattr(settings, 'INTERFAZ_NAME', '')
            pk = get_param_empresa(interfaz, self.empresa_param)

        return pk


class GetOrCreateField(GetOrField):
    def __init__(self, method_name=None, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.method_name = method_name

    def bind(self, field_name, parent):
        super().bind(field_name, parent)
        if self.method_name is None: self.method_name = f'create_{field_name}'

    def to_internal_value(self, data):
        value = super().to_internal_value(data)
        if value is empty:
            try:
                method = getattr(self.parent, self.method_name)
                value = method(self.parent.initial_data)
                if not isinstance(value, self.model):
                    raise ValueError(f'The function {self.method_name} did not return an instance of the model')
                value.save(triggers=False)
            except AbortCreationException:
                return None
        return value
