from django.conf import settings
from django.db import models
from rest_framework import serializers
from rest_framework.exceptions import ValidationError

from isapilib import validators
from isapilib.core.utilities import SerializerData, get_sucursal_from_request
from isapilib.external.utilities import get_almacen, get_sucursal, get_uen
from isapilib.models import Venta, Empresa, Vin, VentaD, Art, CaVenta


def nested_json(attrs: dict, *args, **kwargs):
    serializer = type('NestedJson', (serializers.Serializer,), attrs)
    return serializer(*args, **kwargs)


class CreateSerializer(serializers.Serializer):
    required_fields = []
    priority_fields = []

    _meta = None

    def __init__(self, *args, **kwargs):
        model = getattr(getattr(self, 'Meta', None), 'model', None)
        meta_class = {field: self.get_mapping(model, field) for field in self.required_fields}
        meta_class['Meta'] = getattr(self, 'Meta')
        setattr(self, 'Meta', None)

        super().__init__(*args, **kwargs)
        self.model_serializer_class = type('ModelSerializer', (serializers.ModelSerializer,), meta_class)
        self.serializer = None
        self.base_data = None

    def get_mapping(self, model, field_name):
        field = getattr(getattr(model, field_name), 'field')
        return serializers.ModelSerializer.serializer_field_mapping[type(field)]

    @property
    def meta(self):
        if not hasattr(self, '_meta'):
            self._meta = getattr(self, "Meta", None)

        return self._meta

    @staticmethod
    def normalize_data(data):
        normalized_data = {}
        for name, value in data.items():
            if isinstance(value, models.Model):
                normalized_data[name] = value.pk
            else:
                normalized_data[name] = value

        return normalized_data

    def transform_data(self, attrs):
        self.base_data = SerializerData(attrs)
        serializer = self.model_serializer_class()
        fields = list(dict.fromkeys(self.priority_fields + list(serializer.fields.keys())))

        data = {}
        for field in fields:
            method = getattr(self, "get_%s" % field, None)
            if not method: continue
            data[field] = method(data)

        return data

    def validate(self, attrs):
        transformed_attrs = self.transform_data(attrs)

        try:
            transformed_attrs = self.normalize_data(transformed_attrs)
            self.serializer = self.model_serializer_class(data=transformed_attrs)
            self.serializer.is_valid(raise_exception=True)
        except ValidationError as e:
            raise serializers.ValidationError({
                "detail": "Error while transforming input data to model format",
                "model_errors": e.detail
            })

        return transformed_attrs

    def create(self, validated_data):
        return self.serializer.save()

    def to_representation(self, instance):
        return self.serializer.data


class CreateVentaSerializer(CreateSerializer):
    priority_fields = ['mov', 'sucursal']
    afectar = None

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

        attrs = {'Meta': type('Meta', (), {'model': VentaD, 'fields': '__all__'})}
        self.detail_serializer_class = type('DetailSerializer', (serializers.ModelSerializer,), attrs)

        attrs = {'Meta': type('Meta', (), {'model': CaVenta, 'fields': '__all__'})}
        self.ca_venta_serializer_class = type('CAVentaSerializer', (serializers.ModelSerializer,), attrs)

    def get_empresa(self, _):
        return Empresa.objects.first()

    def get_sucursal(self, data):
        request = self.context['request']
        return get_sucursal(mov=data['mov'], sucursal=get_sucursal_from_request(request))

    def get_sucursal_origen(self, data):
        return data['sucursal']

    def get_almacen(self, data):
        return get_almacen(mov=data['mov'], sucursal=data['sucursal'])

    def get_uen(self, data):
        return get_uen(mov=data['mov'], sucursal=data['sucursal'])

    def get_referencia(self, data):
        return getattr(settings, 'INTERFAZ_NAME', '')

    def transform_data(self, validated_data):
        transformed_data = super().transform_data(validated_data)

        vin = transformed_data.get('servicio_serie')
        if isinstance(vin, Vin):
            transformed_data['servicio_kms'] = vin.km
            transformed_data['servicio_modelo'] = vin.modelo
            transformed_data['servicio_placas'] = vin.placas
            transformed_data['servicio_articulo'] = vin.articulo
            transformed_data['servicio_descripcion'] = vin.color_exterior
            transformed_data['servicio_identificador'] = vin.color_exterior

        self.context['transformed_data'] = transformed_data
        return transformed_data

    def create(self, validated_data):
        instance: Venta = super().create(validated_data)
        transformed_data = self.context['transformed_data']

        if get_details := getattr(self, 'get_details', None):
            for detail in get_details(transformed_data):
                try:
                    self.create_detail(instance, detail)
                except ValidationError as e:
                    raise ValidationError({'details': e.detail})

        if get_ca_venta := getattr(self, 'get_ca_venta', None):
            ca_venta_data = get_ca_venta(transformed_data)
            ca_venta_data['venta'] = instance.pk

            try:
                serializer = self.ca_venta_serializer_class(data=ca_venta_data)
                serializer.is_valid(raise_exception=True)
                serializer.save()
            except ValidationError as e:
                raise ValidationError({'ca_venta': e.detail})

        if self.afectar: instance.afectar(accion=self.afectar)
        return instance

    def get_detail_data(self, instance: Venta, data) -> dict:
        count = VentaD.objects.filter(venta=instance).count() + 1

        articulo: Art = data['articulo']
        cantidad = data.get('cantidad', 1)

        return {
            'renglon_id': count,
            'renglon': 2048 * count,

            'venta': instance,
            'uen': instance.uen,
            'agente': instance.agente,
            'almacen': instance.almacen,
            'sucursal': instance.sucursal,
            'sucursal_origen': instance.sucursal,
            'hora_requerida': instance.hora_requerida,

            'articulo': articulo,
            'impuesto1': articulo.impuesto1,
            'descripcion_extra': articulo.descripcion1,
            'comentarios': articulo.descripcion1,
            'precio': articulo.precio_lista or 0,
            'cantidad': cantidad,

            'cc_tiempo_tab': 1,
            'renglon_tipo': 'N',
            'renglon_sub': 0,
            'ut': 1,
        }

    def create_detail(self, instance: Venta, data):
        base_data = self.get_detail_data(instance, data)
        base_data.update(data)

        detail_data = self.normalize_data(base_data)
        serializer = self.detail_serializer_class(data=detail_data)
        serializer.is_valid(raise_exception=True)
        serializer.save()

    class Meta:
        model = Venta
        fields = '__all__'


class CreateAppointmentSerializer(CreateVentaSerializer):
    required_fields = ['fecha_requerida', 'fecha_emision', 'hora_requerida', 'hora_recepcion']
    priority_fields = CreateVentaSerializer.priority_fields + ['fecha_requerida', 'hora_requerida']

    def get_mov(self, _):
        return 'Cita Servicio'

    def get_fecha_emision(self, data):
        return data['fecha_requerida']

    def get_hora_recepcion(self, data):
        return data['hora_requerida']

    class Meta(CreateVentaSerializer.Meta):
        validators = [
            validators.DateAfterTodayValidator(),
        ]
