from __future__ import annotations

import random
from datetime import datetime, timedelta
from typing import Union, TYPE_CHECKING, List

from rest_framework import status

from isapilib.core import exceptions
from isapilib.core.utilities import get_default_user
from isapilib.external.utilities import execute_sp, get_utc_offset
from isapilib.mov.traspaso import TraspasoItems

if TYPE_CHECKING:
    from isapilib.models import Venta, VentaD


class VentaMixin:
    class ACTIONS:
        AFECTAR = 'AFECTAR'
        CONSECUTIVO = 'CONSECUTIVO'
        GENERAR = 'GENERAR'
        CANCELAR = 'CANCELAR'

    def get_origen(self: Venta) -> Union[Venta, None]:
        from isapilib.models import Venta
        if self.origen is None or self.origen_id is None: return None
        return Venta.objects.filter(mov=self.origen, mov_id=self.origen_id).order_by('-fecha_emision').first()

    def get_destinations(self: Venta) -> Union[Venta, None]:
        from isapilib.models import Venta
        if self.mov is None or self.mov_id is None: return None
        return Venta.objects.filter(origen=self.mov, origen_id=self.mov_id).order_by('-fecha_emision').all()

    def create_parts_order_from_service(self: Venta, usuario=None) -> Venta:
        from isapilib.models import Venta
        usuario = usuario or get_default_user()

        if self.mov != 'Servicio': raise ValueError('La instancia de venta no es un servicio')

        if self.estatus != 'PENDIENTE':
            msg = 'El servicio no esta afectado' if self.estatus == 'SINAFECTAR' else 'El servicio ya esta concluido'
            raise exceptions.RequestError(msg)

        estimated_pk = execute_sp('xpCA_xpMovCopiarSoloEncabezado', {
            'Sucursal': self.sucursal.pk,
            'Modulo': 'VTAS',
            'ID': self.id,
            'Empresa': self.empresa.pk,
            'Mov': 'Pedido Servicio',
            'MovID': self.mov_id,
            'Usuario': usuario,
            'TipoCambio': self.tipo_cambio,
            'Moneda': self.moneda,
            'Almacen': self.almacen.pk,
            'AlmacenDestino': self.almacen.pk,
        })[0][0]

        estimated = Venta.objects.get(id=estimated_pk)
        estimated.origen = self.mov
        estimated.origen_id = self.mov_id
        estimated.save()
        return estimated

    def create_service_from_appointment(self: Venta, action=None, usuario=None) -> Venta:
        from isapilib.models import Venta
        usuario = usuario or get_default_user()

        if self.mov != 'Cita Servicio': raise ValueError('La instancia de venta no es una cita servicio')
        query_order = Venta.objects.filter(origen=self.mov, origen_id=self.mov_id, mov='Servicio').order_by(
            '-fecha_emision')

        order = query_order.first()

        if not self.details.exists():
            raise exceptions.RequestError('La cita no tiene detalles')

        if self.estatus == 'SINAFECTAR':
            self.afectar()

        if order is None:
            code, description = self.afectar(accion=self.ACTIONS.GENERAR, generar='Servicio', usuario=usuario)
            if code != 80030:
                raise exceptions.CoreException(code, description, self)  # ehre
            order = query_order.first()

        if action: order.afectar(accion=action)
        return order

    def traspasar_items(self, details: List[VentaD]):
        if len(details) == 0: return

        order = self.get_origen()
        estacion = random.randint(10000, 99999)

        traspaso = TraspasoItems(order, estacion)

        for detail in details:
            traspaso.add_item(detail)

        traspaso.execute()

    def accept_parts_order(self: Venta, items: tuple[str, bool]) -> bool:
        from isapilib.models import VentaD

        if self.mov != 'Pedido Servicio': raise ValueError('La instancia de venta no es una pedido servicio')
        items_pk = []
        errors = []

        for art_pk, is_accept in items:
            try:
                self.details.get(articulo=art_pk)
                if is_accept: items_pk.append(art_pk)
            except VentaD.DoesNotExist:
                errors.append((art_pk, 'El articulo no existe en el pedido servicio'))

        if 0 < len(errors):
            raise exceptions.RequestError(errors)

        items = list(self.details.filter(articulo__in=items_pk))
        self.traspasar_items(items)

    def cambiar_situacion(self: Venta, situacion, modulo='VTAS', fecha=None, usuario=None, situacion_usuario=None,
                          situacion_nota=None):
        usuario = usuario or get_default_user()

        if fecha is None:
            fecha = datetime.now()
            fecha += timedelta(minutes=get_utc_offset())

        if isinstance(fecha, datetime):
            fecha = fecha.strftime('%Y-%m-%d %H:%M:%S.') + f'{int(fecha.microsecond / 1000):03d}'

        params = [modulo, self.pk, situacion, fecha, usuario, situacion_usuario, situacion_nota]
        execute_sp('spCambiarSituacion', params)
        self.refresh_from_db()
        if self.situacion != situacion: raise Exception(f'Error al cambiar la situacion ({self.pk})')

    def afectar(self: Venta, *, accion=None, generar=None, usuario=None, s=0, modulo=None, estacion=None, base=None):
        usuario = usuario or get_default_user()

        accion = accion or self.ACTIONS.AFECTAR
        code, description = execute_sp('spAfectar', {
            'Modulo': modulo or 'VTAS',
            'ID': self.id,
            'Accion': accion,
            'Base': base or 'Todo',
            'Usuario': usuario,
            'GenerarMov': generar,
            'EnSilencio': s,
            'Estacion': estacion or 1000,
        })[0]

        self.refresh_from_db()

        if (
                (accion == self.ACTIONS.AFECTAR and self.estatus == 'SINAFECTAR') or
                (accion == self.ACTIONS.CONSECUTIVO and self.mov_id is None)
        ):
            raise exceptions.AffectationError(code, description, self.pk, status=status.HTTP_409_CONFLICT)

        return code, description

    def cancelar(self):
        return self.afectar(accion=self.ACTIONS.CANCELAR)
