from django.db import transaction
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils import timezone
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, 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.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.serializers import (
    BooleanField,
    CharField,
    DateTimeField,
    DecimalField,
    DictField,
    IntegerField,
    Serializer,
)
from rest_framework.viewsets import GenericViewSet, ModelViewSet

from apps.boletas.models import Boleta
from apps.boletas.serializers import BoletaSerializer
from apps.expediente.models import Disputa, ExpedienteLog
from apps.expediente.permissions import CanReopenBoleta, CanResolveDisputa, CanSendToERP, CanViewExpediente
from apps.expediente.serializers import DisputaCreateSerializer, DisputaSerializer, DisputaUpdateSerializer
from apps.expediente.utils import (
    build_expediente_payload,
    build_expediente_pdf_bytes,
    build_expediente_zip_bytes,
    simular_envio_erp,
)
from apps.recepciones.models import Recepcion
from apps.recepciones.serializers import RecepcionSerializer
from utils.office import extract_office_id
from utils.pagination import StandardResultsSetPagination


OFFICE_ID_HEADER_PARAMETER = OpenApiParameter(
    name="X-Office-Id",
    type=OpenApiTypes.INT,
    location=OpenApiParameter.HEADER,
    required=False,
    description="Requerido (puede enviarse aquí o como query param 'office_id').",
)
OFFICE_ID_QUERY_PARAMETER = OpenApiParameter(
    name="office_id",
    type=OpenApiTypes.INT,
    location=OpenApiParameter.QUERY,
    required=False,
    description="Requerido (puede enviarse aquí o como header 'X-Office-Id').",
)


ErrorResponseSerializer = inline_serializer(
    name="ExpedienteErrorResponse",
    fields={
        "detail": CharField(required=False),
        "office_id": CharField(required=False),
        "motivo": CharField(required=False),
    },
)


class ExpedienteBoletaProductorSchemaSerializer(Serializer):
    id = IntegerField()
    nombre = CharField()


class ExpedienteBoletaContratoSchemaSerializer(Serializer):
    numero = CharField()
    variedad = CharField(allow_null=True, required=False)


class ExpedienteBoletaZafraSchemaSerializer(Serializer):
    nombre = CharField(allow_null=True, required=False)


class ExpedienteBoletaSchemaSerializer(Serializer):
    id = IntegerField()
    folio = CharField()
    qr = CharField(allow_blank=True, required=False)
    estado = CharField()
    productor = ExpedienteBoletaProductorSchemaSerializer()
    contrato = ExpedienteBoletaContratoSchemaSerializer()
    zafra = ExpedienteBoletaZafraSchemaSerializer()
    fecha_ingreso = DateTimeField(allow_null=True, required=False)


class ExpedienteFotoSchemaSerializer(Serializer):
    tipo = CharField()
    url = CharField(allow_null=True, required=False)
    fecha = DateTimeField(allow_null=True, required=False)


class ExpedienteCamionSchemaSerializer(Serializer):
    placas = CharField(allow_blank=True, required=False)
    chofer = CharField(allow_blank=True, required=False)
    telefono = CharField(allow_blank=True, required=False)
    fotos = ExpedienteFotoSchemaSerializer(many=True)


class ExpedienteRecepcionSchemaSerializer(Serializer):
    id = IntegerField()
    estado = CharField()
    fecha_cierre = DateTimeField(allow_null=True, required=False)
    firmas = DictField(required=False)


class ExpedientePesosDeduccionSchemaSerializer(Serializer):
    parametro = CharField()
    valor_deducido = DecimalField(max_digits=10, decimal_places=2)
    tipo = CharField()


class ExpedientePesosBonificacionSchemaSerializer(Serializer):
    parametro = CharField()
    monto_total = DecimalField(max_digits=12, decimal_places=2)
    monto_por_tonelada = DecimalField(max_digits=12, decimal_places=2)


class ExpedientePesosSchemaSerializer(Serializer):
    lleno = DecimalField(max_digits=10, decimal_places=2, allow_null=True, required=False)
    vacio = DecimalField(max_digits=10, decimal_places=2, allow_null=True, required=False)
    bruto = DecimalField(max_digits=12, decimal_places=3, allow_null=True, required=False)
    deducciones = ExpedientePesosDeduccionSchemaSerializer(many=True)
    bonificaciones = ExpedientePesosBonificacionSchemaSerializer(many=True)
    neto = DecimalField(max_digits=12, decimal_places=3, allow_null=True, required=False)


class ExpedienteMuestreoSchemaSerializer(Serializer):
    tipo = CharField(allow_null=True, required=False)
    tecnico = CharField(allow_null=True, required=False)
    fecha_toma = DateTimeField(allow_null=True, required=False)
    fotos = ExpedienteFotoSchemaSerializer(many=True)


class ExpedienteLabParametroSchemaSerializer(Serializer):
    nombre = CharField()
    valor = CharField(allow_null=True, required=False)
    unidad = CharField(allow_blank=True, required=False)
    fuera_de_rango = BooleanField(required=False)


class ExpedienteResultadosLaboratorioSchemaSerializer(Serializer):
    fecha_analisis = DateTimeField(allow_null=True, required=False)
    estado = CharField(allow_null=True, required=False)
    parametros = ExpedienteLabParametroSchemaSerializer(many=True)
    informe_externo = CharField(allow_null=True, required=False)
    fotos = ExpedienteFotoSchemaSerializer(many=True)


class ExpedienteDocumentosFirmasSchemaSerializer(Serializer):
    receptor = CharField(allow_null=True, required=False)
    chofer = CharField(allow_null=True, required=False)


class ExpedienteDocumentosSchemaSerializer(Serializer):
    pdf_boleta = CharField(allow_null=True, required=False)
    firmas = ExpedienteDocumentosFirmasSchemaSerializer()
    notas = CharField(allow_blank=True, required=False)


class ExpedienteAuditoriaItemSchemaSerializer(Serializer):
    usuario = CharField(allow_null=True, required=False)
    accion = CharField()
    fecha = DateTimeField(allow_null=True, required=False)
    detalle = DictField(required=False)


class ExpedienteBoletaPayloadSchemaSerializer(Serializer):
    boleta = ExpedienteBoletaSchemaSerializer()
    camion = ExpedienteCamionSchemaSerializer()
    recepcion = ExpedienteRecepcionSchemaSerializer(allow_null=True, required=False)
    pesos = ExpedientePesosSchemaSerializer()
    muestreo = ExpedienteMuestreoSchemaSerializer(allow_null=True, required=False)
    resultados_laboratorio = ExpedienteResultadosLaboratorioSchemaSerializer(allow_null=True, required=False)
    documentos = ExpedienteDocumentosSchemaSerializer()
    auditoria = ExpedienteAuditoriaItemSchemaSerializer(many=True)
    mensaje = CharField(required=False)


EnviarERPResponseSerializer = inline_serializer(
    name="ExpedienteEnviarERPResponse",
    fields={
        "success": BooleanField(),
        "mensaje": CharField(),
    },
)


ReabrirBoletaRequestSerializer = inline_serializer(
    name="ExpedienteReabrirBoletaRequest",
    fields={
        "motivo": CharField(),
    },
)


ReabrirBoletaResponseSerializer = inline_serializer(
    name="ExpedienteReabrirBoletaResponse",
    fields={
        "boleta": BoletaSerializer(),
        "recepcion": RecepcionSerializer(),
    },
)


@extend_schema_view(
    retrieve=extend_schema(
        tags=["Expediente"],
        summary="Detalle de expediente por boleta",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        responses={
            200: OpenApiResponse(response=ExpedienteBoletaPayloadSchemaSerializer),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    )
)
class ExpedienteBoletaViewSet(GenericViewSet):
    queryset = Boleta.objects.all()
    serializer_class = ExpedienteBoletaPayloadSchemaSerializer
    pagination_class = StandardResultsSetPagination

    def _get_boleta(self, request, pk: int) -> Boleta:
        office_id = extract_office_id(request)
        if office_id is None:
            raise ValidationError({"office_id": "office_id es requerido."})

        qs = (
            Boleta.objects.select_related(
                "zafra",
                "productor",
                "contrato",
                "variedad",
                "oficina",
            )
            .prefetch_related(
                "recepcion",
                "recepcion__bodega_destino",
                "recepcion__pesos",
                "recepcion__fotos",
                "recepcion__deducciones",
                "recepcion__muestreo",
                "recepcion__muestreo__toma_muestra",
                "recepcion__muestreo__toma_muestra__fotos",
                "recepcion__muestreo__resultado_laboratorio",
                "recepcion__muestreo__resultado_laboratorio__parametros",
                "recepcion__muestreo__resultado_laboratorio__parametros__parametro",
                "recepcion__muestreo__resultado_laboratorio__fotos",
            )
            .filter(oficina_id=office_id)
        )
        return get_object_or_404(qs, pk=pk)

    def get_permissions(self):
        if self.action == "enviar_erp":
            return [CanSendToERP()]
        if self.action == "reabrir":
            return [CanReopenBoleta()]
        return [CanViewExpediente()]

    def retrieve(self, request, pk=None):
        boleta = self._get_boleta(request, int(pk))
        ExpedienteLog.objects.create(
            boleta=boleta,
            usuario=getattr(request, "user", None),
            accion=ExpedienteLog.ACCION_VISTO,
            detalle={},
        )
        payload = build_expediente_payload(boleta=boleta, request=request)
        return Response(payload)

    @extend_schema(
        tags=["Expediente"],
        summary="Generar/mostrar PDF del expediente",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        responses={
            200: OpenApiResponse(response=OpenApiTypes.BINARY, description="Archivo"),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
            500: OpenApiResponse(response=ErrorResponseSerializer, description="No se pudo generar el PDF"),
        },
    )
    @action(detail=True, methods=["get"], url_path="pdf")
    def pdf(self, request, pk=None):
        boleta = self._get_boleta(request, int(pk))
        try:
            pdf_bytes = build_expediente_pdf_bytes(boleta=boleta, request=request)
        except Exception:
            return Response({"detail": "No se pudo generar el PDF del expediente."}, status=500)

        ExpedienteLog.objects.create(
            boleta=boleta,
            usuario=getattr(request, "user", None),
            accion=ExpedienteLog.ACCION_PDF_GENERADO,
            detalle={},
        )

        resp = HttpResponse(pdf_bytes, content_type="application/pdf")
        resp["Content-Disposition"] = f"inline; filename=\"expediente_boleta_{boleta.folio}.pdf\""
        return resp

    @extend_schema(
        tags=["Expediente"],
        summary="Descargar expediente ZIP",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        responses={
            200: OpenApiResponse(response=OpenApiTypes.BINARY, description="Archivo"),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
            500: OpenApiResponse(response=ErrorResponseSerializer, description="No se pudo generar el ZIP"),
        },
    )
    @action(detail=True, methods=["get"], url_path="zip")
    def zip(self, request, pk=None):
        boleta = self._get_boleta(request, int(pk))
        try:
            zip_bytes = build_expediente_zip_bytes(boleta=boleta, request=request)
        except Exception:
            return Response({"detail": "No se pudo generar el ZIP del expediente."}, status=500)

        ExpedienteLog.objects.create(
            boleta=boleta,
            usuario=getattr(request, "user", None),
            accion=ExpedienteLog.ACCION_ZIP_DESCARGADO,
            detalle={},
        )

        resp = HttpResponse(zip_bytes, content_type="application/zip")
        resp["Content-Disposition"] = f"attachment; filename=\"expediente_boleta_{boleta.folio}.zip\""
        return resp

    @extend_schema(
        tags=["Expediente"],
        summary="Enviar expediente a ERP (simulado)",
        description=(
            "Valida que la boleta tenga recepción cerrada y firmas requeridas. "
            "Registra el envío en auditoría (simulado)."
        ),
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        responses={
            200: OpenApiResponse(response=EnviarERPResponseSerializer),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="No se puede enviar"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    )
    @action(detail=True, methods=["post"], url_path="enviar-erp")
    def enviar_erp(self, request, pk=None):
        boleta = self._get_boleta(request, int(pk))

        recepcion = getattr(boleta, "recepcion", None)
        if not recepcion:
            raise ValidationError({"detail": "La boleta aún no tiene recepción."})

        if recepcion.estado != Recepcion.ESTADO_CERRADA:
            raise ValidationError({"detail": "La recepción debe estar cerrada para enviar a ERP."})

        firmas = recepcion.firmas or {}
        if not firmas.get("firma_receptor"):
            raise ValidationError({"detail": "Se requiere firma del receptor."})
        if boleta.chofer_firma_en_dispositivo and not firmas.get("firma_chofer"):
            raise ValidationError({"detail": "Se requiere firma del chofer."})

        result = simular_envio_erp(boleta=boleta)

        ExpedienteLog.objects.create(
            boleta=boleta,
            usuario=getattr(request, "user", None),
            accion=ExpedienteLog.ACCION_ENVIADO_ERP,
            detalle={},
        )
        return Response({"success": result.success, "mensaje": result.mensaje})

    @extend_schema(
        tags=["Expediente"],
        summary="Reabrir boleta/recepción",
        description=(
            "Reabre una recepción en estado final (cerrada/rechazada/escalada) para permitir correcciones. "
            "Cambia la recepción a estado 'borrador' y registra auditoría con motivo."
        ),
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        request=ReabrirBoletaRequestSerializer,
        responses={
            200: OpenApiResponse(response=ReabrirBoletaResponseSerializer),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    )
    @action(detail=True, methods=["post"], url_path="reabrir")
    def reabrir(self, request, pk=None):
        boleta = self._get_boleta(request, int(pk))
        motivo = str(request.data.get("motivo", "")).strip()
        if not motivo:
            raise ValidationError({"motivo": "motivo es requerido."})

        recepcion = getattr(boleta, "recepcion", None)
        if not recepcion:
            raise ValidationError({"detail": "La boleta aún no tiene recepción."})

        if recepcion.estado not in {Recepcion.ESTADO_CERRADA, Recepcion.ESTADO_RECHAZADA, Recepcion.ESTADO_ESCALADA}:
            raise ValidationError({"detail": "La recepción no está en un estado finalizable para reapertura."})

        with transaction.atomic():
            recepcion.estado = Recepcion.ESTADO_BORRADOR
            recepcion.fecha_cierre = None
            recepcion.alerta = {}
            recepcion.updated_by = request.user
            recepcion.save(update_fields=["estado", "fecha_cierre", "alerta", "updated_by", "updated_at"])

            if boleta.estado in {Boleta.ESTADO_RECIBIDA, Boleta.ESTADO_CERRADA}:
                boleta.estado = Boleta.ESTADO_ASIGNADA
                boleta.updated_by = request.user
                boleta.save(update_fields=["estado", "updated_by", "updated_at"])

            ExpedienteLog.objects.create(
                boleta=boleta,
                usuario=getattr(request, "user", None),
                accion=ExpedienteLog.ACCION_REABIERTO,
                detalle={"motivo": motivo, "fecha": timezone.now().isoformat()},
            )

        return Response(
            {
                "boleta": BoletaSerializer(boleta, context={"request": request}).data,
                "recepcion": RecepcionSerializer(recepcion, context={"request": request}).data,
            }
        )

    @extend_schema(
        methods=["GET"],
        tags=["Expediente"],
        summary="Listar disputas de una boleta",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        responses={
            200: OpenApiResponse(response=DisputaSerializer(many=True)),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    )
    @extend_schema(
        methods=["POST"],
        tags=["Expediente"],
        summary="Crear disputa para una boleta",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        request=DisputaCreateSerializer,
        responses={
            201: OpenApiResponse(response=DisputaSerializer),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    )
    @action(detail=True, methods=["get", "post"], url_path="disputas")
    def disputas(self, request, pk=None):
        boleta = self._get_boleta(request, int(pk))

        if request.method.lower() == "get":
            qs = Disputa.objects.filter(boleta=boleta).select_related("creada_por", "resuelta_por").order_by("-id")
            page = self.paginate_queryset(qs)
            if page is not None:
                return self.get_paginated_response(DisputaSerializer(page, many=True).data)
            return Response(DisputaSerializer(qs, many=True).data)

        serializer = DisputaCreateSerializer(data=request.data)
        serializer.is_valid(raise_exception=True)
        disputa = serializer.save(boleta=boleta, creada_por=request.user)

        ExpedienteLog.objects.create(
            boleta=boleta,
            usuario=getattr(request, "user", None),
            accion=ExpedienteLog.ACCION_DISPUTA_REGISTRADA,
            detalle={"disputa_id": disputa.id},
        )

        return Response(DisputaSerializer(disputa).data, status=status.HTTP_201_CREATED)


@extend_schema_view(
    list=extend_schema(
        tags=["Expediente"],
        summary="Listar disputas (por oficina)",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
    ),
    retrieve=extend_schema(
        tags=["Expediente"],
        summary="Detalle de disputa",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
    ),
    create=extend_schema(exclude=True),
    update=extend_schema(
        tags=["Expediente"],
        summary="Actualizar disputa",
        description="Permite cambiar estado y/o resolución. Al cerrar (resuelta/rechazada) asigna resuelta_por y fecha_cierre automáticamente.",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        request=DisputaUpdateSerializer,
        responses={
            200: OpenApiResponse(response=DisputaSerializer),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    ),
    partial_update=extend_schema(
        tags=["Expediente"],
        summary="Actualizar disputa (parcial)",
        description="Permite cambiar estado y/o resolución. Al cerrar (resuelta/rechazada) asigna resuelta_por y fecha_cierre automáticamente.",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        request=DisputaUpdateSerializer,
        responses={
            200: OpenApiResponse(response=DisputaSerializer),
            400: OpenApiResponse(response=ErrorResponseSerializer, description="Solicitud inválida"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    ),
    destroy=extend_schema(
        tags=["Expediente"],
        summary="Eliminar disputa",
        parameters=[OFFICE_ID_HEADER_PARAMETER, OFFICE_ID_QUERY_PARAMETER],
        responses={
            204: OpenApiResponse(description="Eliminado"),
            403: OpenApiResponse(description="Sin permisos"),
            404: OpenApiResponse(description="No encontrado"),
        },
    ),
)
class DisputaViewSet(ModelViewSet):
    queryset = Disputa.objects.select_related("boleta", "creada_por", "resuelta_por").all().order_by("-id")
    serializer_class = DisputaSerializer
    pagination_class = StandardResultsSetPagination

    def get_permissions(self):
        if self.action in {"partial_update", "update", "destroy"}:
            return [CanResolveDisputa()]
        return [CanViewExpediente()]

    def get_queryset(self):
        office_id = extract_office_id(self.request)
        if office_id is None:
            raise ValidationError({"office_id": "office_id es requerido."})
        return super().get_queryset().filter(boleta__oficina_id=office_id)

    def get_serializer_class(self):
        if self.action in {"partial_update", "update"}:
            return DisputaUpdateSerializer
        return DisputaSerializer

    def partial_update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = DisputaUpdateSerializer(instance, data=request.data, partial=True)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        instance.refresh_from_db()
        return Response(DisputaSerializer(instance).data)

    def update(self, request, *args, **kwargs):
        instance = self.get_object()
        serializer = DisputaUpdateSerializer(instance, data=request.data, partial=False)
        serializer.is_valid(raise_exception=True)
        self.perform_update(serializer)
        instance.refresh_from_db()
        return Response(DisputaSerializer(instance).data)

    def perform_update(self, serializer):
        instance: Disputa = serializer.instance
        estado_prev = instance.estado
        obj: Disputa = serializer.save()

        ExpedienteLog.objects.create(
            boleta=obj.boleta,
            usuario=getattr(self.request, "user", None),
            accion=ExpedienteLog.ACCION_DISPUTA_ACTUALIZADA,
            detalle={
                "disputa_id": obj.id,
                "estado_prev": estado_prev,
                "estado": obj.estado,
            },
        )

        cierre_estados = {Disputa.ESTADO_RESUELTA, Disputa.ESTADO_RECHAZADA}
        if obj.estado in cierre_estados and estado_prev not in cierre_estados:
            obj.resuelta_por = self.request.user
            obj.fecha_cierre = timezone.now()
            obj.save(update_fields=["resuelta_por", "fecha_cierre", "updated_at"])
            return

        if obj.estado not in cierre_estados and estado_prev in cierre_estados:
            obj.resuelta_por = None
            obj.fecha_cierre = None
            obj.save(update_fields=["resuelta_por", "fecha_cierre", "updated_at"])
