from decimal import Decimal

import django_filters
from django.http import FileResponse, Http404
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_view, inline_serializer
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.parsers import FormParser, JSONParser, MultiPartParser
from rest_framework.response import Response
from rest_framework.serializers import CharField, ListField
from rest_framework.views import APIView
from rest_framework.viewsets import ModelViewSet

from apps.traspasos.models import AjusteTraspaso, FotoTraspaso, Traspaso
from apps.traspasos.permissions import (
    CanAcceptWithDifference,
    CanCreateTraspaso,
    CanOpenDisputa,
    CanOperateTraspaso,
    CanRegisterAjuste,
    CanViewTraspaso,
)
from apps.usuarios.permissions import IsAdmin
from apps.traspasos.serializers import (
    AjustarTraspasoSerializer,
    DisputaTraspasoSerializer,
    RegistrarLlegadaRequestSerializer,
    RegistrarSalidaRequestSerializer,
    TraspasoCreateSerializer,
    TraspasoSerializer,
)
from apps.traspasos.utils import calcular_diferencia, get_traspaso_margen_porcentaje, registrar_log
from utils.pagination import StandardResultsSetPagination


ErrorDetailSerializer = inline_serializer(
    name="ErrorDetail",
    fields={
        "detail": CharField(),
    },
)


TraspasoRegistrarLlegadaAlertaResponseSerializer = inline_serializer(
    name="TraspasoRegistrarLlegadaAlertaResponse",
    fields={
        "alerta": inline_serializer(
            name="TraspasoRegistrarLlegadaAlerta",
            fields={
                "tipo": CharField(),
                "mensaje": CharField(),
                "opciones": ListField(child=CharField()),
            },
        )
    },
)


class TraspasoFilter(django_filters.FilterSet):
    created_date = django_filters.DateFilter(field_name="created_at", lookup_expr="date")
    created_date_gte = django_filters.DateFilter(field_name="created_at", lookup_expr="date__gte")
    created_date_lte = django_filters.DateFilter(field_name="created_at", lookup_expr="date__lte")

    fecha_programada_date = django_filters.DateFilter(field_name="fecha_programada", lookup_expr="date")
    fecha_programada_gte = django_filters.DateFilter(field_name="fecha_programada", lookup_expr="date__gte")
    fecha_programada_lte = django_filters.DateFilter(field_name="fecha_programada", lookup_expr="date__lte")

    class Meta:
        model = Traspaso
        fields = {
            "estado": ["exact"],
            "origen": ["exact"],
            "destino": ["exact"],
        }


class FotoTraspasoDownloadView(APIView):
    permission_classes = [CanOperateTraspaso]

    @extend_schema(
        tags=["Traspasos"],
        summary="Descargar foto de traspaso",
        description="Devuelve el archivo asociado a una foto (binario).",
        responses={
            200: OpenApiResponse(response=OpenApiTypes.BINARY, description="Archivo"),
            404: OpenApiResponse(response=ErrorDetailSerializer, description="No encontrado"),
        },
    )
    def get(self, request, pk: int):
        try:
            foto = FotoTraspaso.objects.select_related("traspaso").get(pk=pk)
        except FotoTraspaso.DoesNotExist as e:
            raise Http404() from e

        if not foto.imagen:
            raise Http404()

        resp = FileResponse(foto.imagen.open("rb"), content_type="application/octet-stream")
        resp["Content-Disposition"] = f"inline; filename=\"{foto.imagen.name.split('/')[-1]}\""
        return resp


@extend_schema_view(
    list=extend_schema(tags=["Traspasos"]),
    retrieve=extend_schema(tags=["Traspasos"]),
    create=extend_schema(
        tags=["Traspasos"],
        request=TraspasoCreateSerializer,
        responses={
            201: OpenApiResponse(response=TraspasoSerializer),
            400: OpenApiResponse(response=ErrorDetailSerializer, description="Error de validación"),
            403: OpenApiResponse(description="Sin permisos"),
        },
    ),
    update=extend_schema(tags=["Traspasos"]),
    partial_update=extend_schema(tags=["Traspasos"]),
    destroy=extend_schema(tags=["Traspasos"]),
)
class TraspasoViewSet(ModelViewSet):
    queryset = (
        Traspaso.objects.select_related("origen", "destino", "creado_por", "actualizado_por")
        .prefetch_related("items", "fotos", "logs", "ajuste")
        .all()
        .order_by("-id")
    )
    serializer_class = TraspasoSerializer
    pagination_class = StandardResultsSetPagination
    filterset_class = TraspasoFilter
    ordering_fields = ["id", "created_at", "updated_at", "fecha_programada"]

    def get_permissions(self):
        if self.action in {"list", "retrieve"}:
            return [CanViewTraspaso()]
        if self.action == "create":
            return [CanCreateTraspaso()]
        if self.action == "destroy":
            return [IsAdmin()]
        if self.action in {"registrar_salida", "registrar_llegada"}:
            return [CanOperateTraspaso()]
        if self.action == "aceptar":
            return [CanAcceptWithDifference()]
        if self.action == "ajustar":
            return [CanRegisterAjuste()]
        if self.action == "disputa":
            return [CanOpenDisputa()]
        return [CanOperateTraspaso()]

    def get_serializer_class(self):
        if self.action == "create":
            return TraspasoCreateSerializer
        return TraspasoSerializer

    def create(self, request, *args, **kwargs):
        serializer = TraspasoCreateSerializer(data=request.data, context={"user": request.user})
        serializer.is_valid(raise_exception=True)
        traspaso = serializer.save()
        registrar_log(traspaso=traspaso, usuario=request.user, accion="creado", detalle={})
        out = TraspasoSerializer(traspaso, context={"request": request}).data
        return Response(out, status=status.HTTP_201_CREATED)

    @action(
        detail=True,
        methods=["post"],
        url_path="registrar-salida",
        parser_classes=[JSONParser, MultiPartParser, FormParser],
    )
    @extend_schema(
        tags=["Traspasos"],
        summary="Registrar salida",
        description="Registra el pesaje de salida, firma del conductor (opcional) y fotos (opcional).",
        request=RegistrarSalidaRequestSerializer,
        responses={
            200: OpenApiResponse(response=TraspasoSerializer),
            400: OpenApiResponse(response=ErrorDetailSerializer, description="Error de validación"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(response=ErrorDetailSerializer, description="No encontrado"),
        },
    )
    def registrar_salida(self, request, pk=None):
        traspaso: Traspaso = self.get_object()
        if traspaso.estado != Traspaso.ESTADO_PENDIENTE_SALIDA:
            raise ValidationError({"detail": "El traspaso debe estar en estado pendiente_salida."})

        data = request.data.copy() if hasattr(request.data, "copy") else dict(request.data)
        fotos_files = request.FILES.getlist("fotos")
        if fotos_files:
            if hasattr(data, "setlist"):
                data.setlist("fotos", fotos_files)
            else:
                data["fotos"] = fotos_files

        serializer = RegistrarSalidaRequestSerializer(data=data)
        serializer.is_valid(raise_exception=True)

        peso_salida = serializer.validated_data["peso_salida"]
        firma = serializer.validated_data.get("firma_conductor", "")

        traspaso.peso_salida = peso_salida
        if firma:
            traspaso.firma_conductor_salida = firma
        traspaso.estado = Traspaso.ESTADO_SALIDA_REGISTRADA
        traspaso.actualizado_por = request.user
        traspaso.save(
            update_fields=[
                "peso_salida",
                "firma_conductor_salida",
                "estado",
                "actualizado_por",
                "updated_at",
            ]
        )

        fotos = serializer.validated_data.get("fotos") or []
        for f in fotos:
            FotoTraspaso.objects.create(traspaso=traspaso, tipo="carga_salida", imagen=f)

        registrar_log(
            traspaso=traspaso,
            usuario=request.user,
            accion="pesaje_salida",
            detalle={
                "peso_salida": str(peso_salida),
                "es_automatico": bool(serializer.validated_data.get("es_automatico", True)),
                "motivo_manual": serializer.validated_data.get("motivo_manual", ""),
                "fotos": len(fotos),
            },
        )

        traspaso.refresh_from_db()
        return Response(TraspasoSerializer(traspaso, context={"request": request}).data)

    @action(
        detail=True,
        methods=["post"],
        url_path="registrar-llegada",
        parser_classes=[JSONParser, MultiPartParser, FormParser],
    )
    @extend_schema(
        tags=["Traspasos"],
        summary="Registrar llegada",
        description="Registra el pesaje de llegada, firma del conductor (opcional) y fotos (opcional). Si la diferencia excede el margen, devuelve 409 con una alerta y opciones.",
        request=RegistrarLlegadaRequestSerializer,
        responses={
            200: OpenApiResponse(response=TraspasoSerializer),
            400: OpenApiResponse(response=ErrorDetailSerializer, description="Error de validación"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(response=ErrorDetailSerializer, description="No encontrado"),
            409: OpenApiResponse(
                response=TraspasoRegistrarLlegadaAlertaResponseSerializer,
                description="Diferencia excesiva (requiere decisión)",
            ),
        },
    )
    def registrar_llegada(self, request, pk=None):
        traspaso: Traspaso = self.get_object()
        if traspaso.estado != Traspaso.ESTADO_SALIDA_REGISTRADA:
            raise ValidationError({"detail": "El traspaso debe estar en estado salida_registrada."})
        if not traspaso.peso_salida or Decimal(str(traspaso.peso_salida)) <= 0:
            raise ValidationError({"detail": "Debe existir peso_salida antes de registrar llegada."})

        data = request.data.copy() if hasattr(request.data, "copy") else dict(request.data)
        fotos_files = request.FILES.getlist("fotos")
        if fotos_files:
            if hasattr(data, "setlist"):
                data.setlist("fotos", fotos_files)
            else:
                data["fotos"] = fotos_files

        serializer = RegistrarLlegadaRequestSerializer(data=data)
        serializer.is_valid(raise_exception=True)

        peso_llegada = serializer.validated_data["peso_llegada"]
        firma = serializer.validated_data.get("firma_conductor", "")

        diferencia = calcular_diferencia(Decimal(str(traspaso.peso_salida)), Decimal(str(peso_llegada)))

        traspaso.peso_llegada = peso_llegada
        traspaso.diferencia = diferencia
        if firma:
            traspaso.firma_conductor_llegada = firma

        margen = get_traspaso_margen_porcentaje()
        exceso = abs(diferencia) > margen

        traspaso.estado = Traspaso.ESTADO_PENDIENTE_LLEGADA if exceso else Traspaso.ESTADO_COMPLETADO
        traspaso.actualizado_por = request.user
        traspaso.save(
            update_fields=[
                "peso_llegada",
                "diferencia",
                "firma_conductor_llegada",
                "estado",
                "actualizado_por",
                "updated_at",
            ]
        )

        fotos = serializer.validated_data.get("fotos") or []
        for f in fotos:
            FotoTraspaso.objects.create(traspaso=traspaso, tipo="descarga_llegada", imagen=f)

        registrar_log(
            traspaso=traspaso,
            usuario=request.user,
            accion="pesaje_llegada",
            detalle={
                "peso_llegada": str(peso_llegada),
                "diferencia_pct": str(diferencia),
                "margen_pct": str(margen),
                "fotos": len(fotos),
            },
        )

        if exceso:
            return Response(
                {
                    "alerta": {
                        "tipo": "diferencia_excesiva",
                        "mensaje": f"La diferencia es del {diferencia}% supera el margen del {margen}%.",
                        "opciones": ["aceptar_traspaso", "registrar_ajuste", "abrir_disputa"],
                    }
                },
                status=status.HTTP_409_CONFLICT,
            )

        registrar_log(traspaso=traspaso, usuario=request.user, accion="completado", detalle={"auto": True})

        traspaso.refresh_from_db()
        return Response(TraspasoSerializer(traspaso, context={"request": request}).data)

    @action(detail=True, methods=["post"], url_path="aceptar")
    @extend_schema(
        tags=["Traspasos"],
        summary="Aceptar traspaso con diferencia",
        description="Acepta un traspaso cuando está pendiente de decisión por diferencia excesiva.",
        request=None,
        responses={
            200: OpenApiResponse(response=TraspasoSerializer),
            400: OpenApiResponse(response=ErrorDetailSerializer, description="Error de validación"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(response=ErrorDetailSerializer, description="No encontrado"),
        },
    )
    def aceptar(self, request, pk=None):
        traspaso: Traspaso = self.get_object()
        if traspaso.estado != Traspaso.ESTADO_PENDIENTE_LLEGADA:
            raise ValidationError({"detail": "El traspaso no está pendiente de decisión."})
        if traspaso.peso_llegada is None:
            raise ValidationError({"detail": "Debe registrar llegada antes de aceptar."})

        traspaso.estado = Traspaso.ESTADO_COMPLETADO
        traspaso.actualizado_por = request.user
        traspaso.save(update_fields=["estado", "actualizado_por", "updated_at"])

        registrar_log(traspaso=traspaso, usuario=request.user, accion="aceptado", detalle={})
        traspaso.refresh_from_db()
        return Response(TraspasoSerializer(traspaso, context={"request": request}).data)

    @action(detail=True, methods=["post"], url_path="ajustar")
    @extend_schema(
        tags=["Traspasos"],
        summary="Registrar ajuste",
        description="Registra un ajuste (kilos y motivo) cuando el traspaso está pendiente de decisión.",
        request=AjustarTraspasoSerializer,
        responses={
            200: OpenApiResponse(response=TraspasoSerializer),
            400: OpenApiResponse(response=ErrorDetailSerializer, description="Error de validación"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(response=ErrorDetailSerializer, description="No encontrado"),
        },
    )
    def ajustar(self, request, pk=None):
        traspaso: Traspaso = self.get_object()
        if traspaso.estado != Traspaso.ESTADO_PENDIENTE_LLEGADA:
            raise ValidationError({"detail": "El traspaso no está pendiente de decisión."})
        if traspaso.peso_llegada is None:
            raise ValidationError({"detail": "Debe registrar llegada antes de ajustar."})

        serializer = AjustarTraspasoSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        if AjusteTraspaso.objects.filter(traspaso=traspaso).exists():
            raise ValidationError({"detail": "El traspaso ya tiene un ajuste registrado."})

        ajuste = AjusteTraspaso.objects.create(
            traspaso=traspaso,
            kilos_ajustados=serializer.validated_data["kilos_ajustados"],
            motivo=serializer.validated_data["motivo"],
            aprobado_por=request.user,
        )

        traspaso.estado = Traspaso.ESTADO_AJUSTADO
        traspaso.actualizado_por = request.user
        traspaso.save(update_fields=["estado", "actualizado_por", "updated_at"])

        registrar_log(
            traspaso=traspaso,
            usuario=request.user,
            accion="ajustado",
            detalle={
                "kilos_ajustados": str(ajuste.kilos_ajustados),
                "motivo": ajuste.motivo,
                "ajuste_id": ajuste.id,
            },
        )

        traspaso.refresh_from_db()
        return Response(TraspasoSerializer(traspaso, context={"request": request}).data)

    @action(detail=True, methods=["post"], url_path="disputa")
    @extend_schema(
        tags=["Traspasos"],
        summary="Abrir disputa",
        description="Abre una disputa (con motivo) cuando el traspaso está pendiente de decisión.",
        request=DisputaTraspasoSerializer,
        responses={
            200: OpenApiResponse(response=TraspasoSerializer),
            400: OpenApiResponse(response=ErrorDetailSerializer, description="Error de validación"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(response=ErrorDetailSerializer, description="No encontrado"),
        },
    )
    def disputa(self, request, pk=None):
        traspaso: Traspaso = self.get_object()
        if traspaso.estado != Traspaso.ESTADO_PENDIENTE_LLEGADA:
            raise ValidationError({"detail": "El traspaso no está pendiente de decisión."})
        if traspaso.peso_llegada is None:
            raise ValidationError({"detail": "Debe registrar llegada antes de abrir disputa."})

        serializer = DisputaTraspasoSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)

        traspaso.estado = Traspaso.ESTADO_DISPUTA
        traspaso.actualizado_por = request.user
        traspaso.save(update_fields=["estado", "actualizado_por", "updated_at"])

        registrar_log(
            traspaso=traspaso,
            usuario=request.user,
            accion="disputa",
            detalle={"motivo": serializer.validated_data["motivo"]},
        )

        traspaso.refresh_from_db()
        return Response(TraspasoSerializer(traspaso, context={"request": request}).data)
