from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from decimal import Decimal

from django.db.models import Prefetch
from django.utils.dateparse import parse_datetime
from drf_spectacular.utils import OpenApiResponse, extend_schema, inline_serializer
from rest_framework.response import Response
from rest_framework.serializers import CharField
from rest_framework.views import APIView

from apps.boletas.models import Boleta
from apps.movimientos.permissions import CanViewMovimientos
from apps.recepciones.models import Deduccion, PesoRecepcion, Recepcion
from apps.traspasos.models import ItemTraspaso, Traspaso
from apps.movimientos.serializers import MovimientoSerializer
from utils.office import extract_office_id
from utils.pagination import StandardResultsSetPagination


@dataclass(frozen=True)
class _DateRange:
    desde: datetime | None
    hasta: datetime | None


def _parse_date_range(request) -> _DateRange:
    qp = getattr(request, "query_params", {}) or {}

    desde_raw = qp.get("desde") or qp.get("desde_fecha")
    hasta_raw = qp.get("hasta") or qp.get("hasta_fecha")

    desde = parse_datetime(str(desde_raw)) if desde_raw else None
    hasta = parse_datetime(str(hasta_raw)) if hasta_raw else None

    return _DateRange(desde=desde, hasta=hasta)


def _peso_neto_recepcion(recepcion: Recepcion) -> tuple[Decimal, Decimal, Decimal]:
    pesos = list(getattr(recepcion, "pesos", []).all() if hasattr(recepcion, "pesos") else [])
    lleno = next((p.valor for p in pesos if p.tipo == PesoRecepcion.TIPO_LLENO), None)
    vacio = next((p.valor for p in pesos if p.tipo == PesoRecepcion.TIPO_VACIO), None)
    if lleno is None or vacio is None:
        return Decimal("0"), Decimal("0"), Decimal("0")

    bruto = Decimal(lleno) - Decimal(vacio)

    deducciones = list(getattr(recepcion, "deducciones", []).all() if hasattr(recepcion, "deducciones") else [])
    total_deducido = Decimal("0")
    for d in deducciones:
        if d.tipo_deduccion == "fijo":
            total_deducido += Decimal(d.valor_deducido)
        elif d.tipo_deduccion == "porcentaje":
            total_deducido += (bruto * Decimal(d.valor_deducido)) / Decimal("100")

    neto = bruto - total_deducido
    if neto < 0:
        neto = Decimal("0")

    return bruto, total_deducido, neto


MovimientosListResponseSerializer = inline_serializer(
    name="MovimientosListResponse",
    fields={
        "detail": CharField(required=False),
    },
)


class MovimientosListView(APIView):
    permission_classes = [CanViewMovimientos]
    pagination_class = StandardResultsSetPagination

    @extend_schema(
        tags=["Movimientos"],
        summary="Listar movimientos (solo log; no inventario)",
        description=(
            "Lista movimientos derivados de recepciones cerradas y traspasos completados/ajustados. "
            "Filtros soportados: tipo=recepcion|traspaso, bodega_id, variedad_id, productor_id, boleta_id, traspaso_id, desde, hasta. "
            "Requiere office_id."
        ),
        responses={200: OpenApiResponse(response=MovimientoSerializer(many=True))},
    )
    def get(self, request):
        office_id = extract_office_id(request)
        if office_id is None:
            return Response({"detail": "office_id es requerido."}, status=400)

        qp = getattr(request, "query_params", {}) or {}

        tipo = str(qp.get("tipo") or "").strip().lower()
        bodega_id = qp.get("bodega_id")
        variedad_id = qp.get("variedad_id")
        productor_id = qp.get("productor_id")
        boleta_id = qp.get("boleta_id")
        traspaso_id = qp.get("traspaso_id")

        try:
            bodega_id_int = int(bodega_id) if bodega_id not in (None, "") else None
        except (TypeError, ValueError):
            return Response({"detail": "bodega_id inválido."}, status=400)

        try:
            variedad_id_int = int(variedad_id) if variedad_id not in (None, "") else None
        except (TypeError, ValueError):
            return Response({"detail": "variedad_id inválido."}, status=400)

        try:
            productor_id_int = int(productor_id) if productor_id not in (None, "") else None
        except (TypeError, ValueError):
            return Response({"detail": "productor_id inválido."}, status=400)

        try:
            boleta_id_int = int(boleta_id) if boleta_id not in (None, "") else None
        except (TypeError, ValueError):
            return Response({"detail": "boleta_id inválido."}, status=400)

        try:
            traspaso_id_int = int(traspaso_id) if traspaso_id not in (None, "") else None
        except (TypeError, ValueError):
            return Response({"detail": "traspaso_id inválido."}, status=400)

        dr = _parse_date_range(request)

        movimientos: list[dict] = []

        if tipo in {"", "recepcion"}:
            recepciones_qs = (
                Recepcion.objects.select_related(
                    "boleta",
                    "boleta__productor",
                    "boleta__contrato",
                    "boleta__variedad",
                    "bodega_destino",
                )
                .filter(
                    oficina_id=office_id,
                    estado=Recepcion.ESTADO_CERRADA,
                    boleta__estado__in=[Boleta.ESTADO_RECIBIDA, Boleta.ESTADO_CERRADA],
                )
                .prefetch_related(
                    "pesos",
                    Prefetch("deducciones", queryset=Deduccion.objects.select_related("parametro")),
                )
                .order_by("-id")
            )

            if boleta_id_int is not None:
                recepciones_qs = recepciones_qs.filter(boleta_id=boleta_id_int)
            if productor_id_int is not None:
                recepciones_qs = recepciones_qs.filter(boleta__productor_id=productor_id_int)
            if variedad_id_int is not None:
                recepciones_qs = recepciones_qs.filter(boleta__variedad_id=variedad_id_int)
            if bodega_id_int is not None:
                recepciones_qs = recepciones_qs.filter(bodega_destino_id=bodega_id_int)

            if dr.desde is not None:
                recepciones_qs = recepciones_qs.filter(fecha_cierre__gte=dr.desde)
            if dr.hasta is not None:
                recepciones_qs = recepciones_qs.filter(fecha_cierre__lte=dr.hasta)

            for r in recepciones_qs:
                boleta = r.boleta
                contrato = getattr(boleta, "contrato", None)
                variedad = getattr(contrato, "variedad", None) if contrato else None

                bruto, total_deducido, neto = _peso_neto_recepcion(r)

                movimientos.append(
                    {
                        "id": f"R-{r.id}",
                        "tipo": "recepcion",
                        "fecha": r.fecha_cierre,
                        "oficina_id": office_id,
                        "documento": {"tipo": "boleta", "id": boleta.id, "folio": str(boleta.folio)},
                        "productor": {
                            "id": boleta.productor_id,
                            "nombre": getattr(getattr(boleta, "productor", None), "nombre", ""),
                        },
                        "bodega_destino": {
                            "id": getattr(r, "bodega_destino_id", None),
                            "codigo": getattr(getattr(r, "bodega_destino", None), "codigo", ""),
                            "nombre": getattr(getattr(r, "bodega_destino", None), "nombre", ""),
                        },
                        "variedad": {
                            "id": getattr(variedad, "id", None),
                            "nombre": getattr(variedad, "nombre", ""),
                        },
                        "kilos_esperados": getattr(boleta, "peso_estimado", None),
                        "kilos_reales": neto,
                        "detalle": {
                            "peso_bruto": str(bruto),
                            "total_deducido": str(total_deducido),
                        },
                    }
                )

        if tipo in {"", "traspaso"}:
            estados_ok = [Traspaso.ESTADO_COMPLETADO, Traspaso.ESTADO_AJUSTADO]
            traspasos_qs = (
                Traspaso.objects.select_related("origen", "destino")
                .prefetch_related(
                    Prefetch("items", queryset=ItemTraspaso.objects.select_related("variedad", "boleta_referencia"))
                )
                .filter(estado__in=estados_ok)
                .order_by("-id")
            )

            if traspaso_id_int is not None:
                traspasos_qs = traspasos_qs.filter(id=traspaso_id_int)

            if dr.desde is not None:
                traspasos_qs = traspasos_qs.filter(updated_at__gte=dr.desde)
            if dr.hasta is not None:
                traspasos_qs = traspasos_qs.filter(updated_at__lte=dr.hasta)

            for t in traspasos_qs:
                origen_oficina_id = getattr(t.origen, "oficina_id", None)
                destino_oficina_id = getattr(t.destino, "oficina_id", None)

                if origen_oficina_id != office_id and destino_oficina_id != office_id:
                    continue

                if bodega_id_int is not None and bodega_id_int not in {t.origen_id, t.destino_id}:
                    continue

                for it in t.items.all():
                    if variedad_id_int is not None and int(it.variedad_id) != int(variedad_id_int):
                        continue

                    kilos_real = it.kilos_reales if it.kilos_reales is not None else it.kilos_programados

                    base = {
                        "fecha": t.updated_at,
                        "oficina_id": office_id,
                        "documento": {"tipo": "traspaso", "id": t.id, "folio": t.folio},
                        "variedad": {"id": it.variedad_id, "nombre": getattr(it.variedad, "nombre", "")},
                        "kilos_esperados": it.kilos_programados,
                        "kilos_reales": kilos_real,
                        "detalle": {"estado": t.estado},
                    }

                    movimientos.append(
                        {
                            **base,
                            "id": f"T-{t.id}-I-{it.id}-S",
                            "tipo": "traspaso_salida",
                            "bodega_origen": {
                                "id": t.origen_id,
                                "codigo": getattr(t.origen, "codigo", ""),
                                "nombre": getattr(t.origen, "nombre", ""),
                            },
                            "bodega_destino": {
                                "id": t.destino_id,
                                "codigo": getattr(t.destino, "codigo", ""),
                                "nombre": getattr(t.destino, "nombre", ""),
                            },
                        }
                    )

                    movimientos.append(
                        {
                            **base,
                            "id": f"T-{t.id}-I-{it.id}-E",
                            "tipo": "traspaso_entrada",
                            "bodega_origen": {
                                "id": t.origen_id,
                                "codigo": getattr(t.origen, "codigo", ""),
                                "nombre": getattr(t.origen, "nombre", ""),
                            },
                            "bodega_destino": {
                                "id": t.destino_id,
                                "codigo": getattr(t.destino, "codigo", ""),
                                "nombre": getattr(t.destino, "nombre", ""),
                            },
                        }
                    )

        if tipo and tipo not in {"recepcion", "traspaso"}:
            return Response({"detail": "tipo inválido. Use recepcion|traspaso."}, status=400)

        movimientos.sort(key=lambda x: x.get("fecha") or datetime.min, reverse=True)

        paginator = self.pagination_class()
        page = paginator.paginate_queryset(movimientos, request, view=self)
        serializer = MovimientoSerializer(page, many=True)
        return paginator.get_paginated_response(serializer.data)
