from __future__ import annotations

import logging

from django.core.exceptions import PermissionDenied
from django.http import Http404
from drf_spectacular.utils import OpenApiExample, OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework import serializers, status
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from istat.xml_export.api.serializers import (
    C59XmlExportRequestSerializer,
    C59ZipExportRequestSerializer,
    GuestXmlExportRequestSerializer,
)
from istat.xml_export.application.export_facade import (
    generate_c59_xml_download,
    generate_c59_zip_download,
    generate_guest_xml_download,
)
from istat.xml_export.exceptions import XmlPayloadValidationError
from structures.models import Structure


logger = logging.getLogger(__name__)


def _get_accessible_structure_or_raise(*, user, structure_id: int) -> Structure:
    structure = Structure.objects.filter(id=structure_id).first()
    if structure is None:
        raise Http404("Structure not found.")

    has_access = (
        structure.user_id == user.id
        or structure.structure_users.filter(user=user).exists()
    )
    if not has_access:
        raise PermissionDenied("You do not have access to this structure.")

    return structure


class XmlExportAPIViewBase(APIView):
    permission_classes = [IsAuthenticated]
    request_serializer_class: type[serializers.Serializer] | None = None

    def _validate_query(self, request):
        serializer_class = self.request_serializer_class
        if serializer_class is None:
            raise RuntimeError("request_serializer_class must be configured")
        serializer = serializer_class(data=request.query_params)
        serializer.is_valid(raise_exception=True)
        return serializer.validated_data


class GuestXmlExportAPIView(XmlExportAPIViewBase):
    request_serializer_class = GuestXmlExportRequestSerializer

    @extend_schema(
        tags=["ISTAT XML"],
        summary="Download guest movement XML export",
        parameters=[
            OpenApiParameter(
                name="structure_id",
                type=int,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Structure ID to export.",
            ),
            OpenApiParameter(
                name="start_date",
                type=str,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Start date in YYYY-MM-DD format.",
            ),
            OpenApiParameter(
                name="end_date",
                type=str,
                location=OpenApiParameter.QUERY,
                required=True,
                description="End date in YYYY-MM-DD format.",
            ),
        ],
        examples=[
            OpenApiExample(
                "Guest XML export",
                value={
                    "structure_id": 1,
                    "start_date": "2026-05-01",
                    "end_date": "2026-05-03",
                },
                parameter_only=("structure_id", "query"),
            )
        ],
        responses={
            200: OpenApiResponse(description="Downloadable guest XML response"),
            400: OpenApiResponse(description="Validation or business-rule error"),
            403: OpenApiResponse(description="Permission denied"),
            404: OpenApiResponse(description="Structure not found"),
            500: OpenApiResponse(description="Unexpected server error"),
        },
    )
    def get(self, request):
        try:
            params = self._validate_query(request)
            _get_accessible_structure_or_raise(
                user=request.user,
                structure_id=params["structure_id"],
            )
            response = generate_guest_xml_download(**params)
        except serializers.ValidationError as exc:
            return Response(exc.detail, status=status.HTTP_400_BAD_REQUEST)
        except XmlPayloadValidationError as exc:
            logger.warning(
                "Guest XML export API failed",
                extra={
                    "user_id": request.user.id,
                    "structure_id": request.query_params.get("structure_id"),
                    "start_date": request.query_params.get("start_date"),
                    "end_date": request.query_params.get("end_date"),
                    "export_type": "guest_xml",
                    "status": "failure",
                },
            )
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        except PermissionDenied as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_403_FORBIDDEN)
        except Http404 as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)
        except Exception:
            logger.exception(
                "Guest XML export API unexpected failure",
                extra={
                    "user_id": request.user.id,
                    "structure_id": request.query_params.get("structure_id"),
                    "start_date": request.query_params.get("start_date"),
                    "end_date": request.query_params.get("end_date"),
                    "export_type": "guest_xml",
                    "status": "failure",
                },
            )
            return Response(
                {"detail": "An unexpected error occurred."},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

        logger.info(
            "Guest XML export API succeeded",
            extra={
                "user_id": request.user.id,
                "structure_id": params["structure_id"],
                "start_date": params["start_date"].isoformat(),
                "end_date": params["end_date"].isoformat(),
                "export_type": "guest_xml",
                "status": "success",
            },
        )
        return response


class C59XmlExportAPIView(XmlExportAPIViewBase):
    request_serializer_class = C59XmlExportRequestSerializer

    @extend_schema(
        tags=["ISTAT XML"],
        summary="Download C59 XML export",
        parameters=[
            OpenApiParameter(
                name="structure_id",
                type=int,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Structure ID to export.",
            ),
            OpenApiParameter(
                name="report_date",
                type=str,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Report date in YYYY-MM-DD format.",
            ),
            OpenApiParameter(
                name="validate_xsd",
                type=bool,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Whether to validate the generated XML against the C59 XSD.",
            ),
            OpenApiParameter(
                name="xsd_path",
                type=str,
                location=OpenApiParameter.QUERY,
                required=False,
                description="Optional explicit filesystem path to the C59 XSD schema file.",
            ),
        ],
        examples=[
            OpenApiExample(
                "C59 XML export",
                value={
                    "structure_id": 1,
                    "report_date": "2026-05-01",
                    "validate_xsd": True,
                },
                parameter_only=("structure_id", "query"),
            )
        ],
        responses={
            200: OpenApiResponse(description="Downloadable C59 XML response"),
            400: OpenApiResponse(description="Validation or business-rule error"),
            403: OpenApiResponse(description="Permission denied"),
            404: OpenApiResponse(description="Structure not found"),
            500: OpenApiResponse(description="Unexpected server error"),
        },
    )
    def get(self, request):
        try:
            params = self._validate_query(request)
            _get_accessible_structure_or_raise(
                user=request.user,
                structure_id=params["structure_id"],
            )
            response = generate_c59_xml_download(**params)
        except serializers.ValidationError as exc:
            return Response(exc.detail, status=status.HTTP_400_BAD_REQUEST)
        except XmlPayloadValidationError as exc:
            logger.warning(
                "C59 XML export API failed",
                extra={
                    "user_id": request.user.id,
                    "structure_id": request.query_params.get("structure_id"),
                    "report_date": request.query_params.get("report_date"),
                    "validate_xsd": request.query_params.get("validate_xsd"),
                    "export_type": "c59_xml",
                    "status": "failure",
                },
            )
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        except PermissionDenied as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_403_FORBIDDEN)
        except Http404 as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)
        except Exception:
            logger.exception(
                "C59 XML export API unexpected failure",
                extra={
                    "user_id": request.user.id,
                    "structure_id": request.query_params.get("structure_id"),
                    "report_date": request.query_params.get("report_date"),
                    "validate_xsd": request.query_params.get("validate_xsd"),
                    "export_type": "c59_xml",
                    "status": "failure",
                },
            )
            return Response(
                {"detail": "An unexpected error occurred."},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

        logger.info(
            "C59 XML export API succeeded",
            extra={
                "user_id": request.user.id,
                "structure_id": params["structure_id"],
                "report_date": params["report_date"].isoformat(),
                "validate_xsd": params["validate_xsd"],
                "export_type": "c59_xml",
                "status": "success",
            },
        )
        return response


class C59ZipExportAPIView(XmlExportAPIViewBase):
    """Download a ZIP archive containing one C59 XML file per calendar day."""

    request_serializer_class = C59ZipExportRequestSerializer

    @extend_schema(
        tags=["ISTAT XML"],
        summary="Download C59 ZIP export (one XML per day)",
        parameters=[
            OpenApiParameter(
                name="structure_id",
                type=int,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Structure ID to export.",
            ),
            OpenApiParameter(
                name="start_date",
                type=str,
                location=OpenApiParameter.QUERY,
                required=True,
                description="First day of the reporting period (YYYY-MM-DD, inclusive).",
            ),
            OpenApiParameter(
                name="end_date",
                type=str,
                location=OpenApiParameter.QUERY,
                required=True,
                description="Last day of the reporting period (YYYY-MM-DD, inclusive).",
            ),
        ],
        examples=[
            OpenApiExample(
                "C59 ZIP export — June 2026",
                value={
                    "structure_id": 1,
                    "start_date": "2026-06-01",
                    "end_date": "2026-06-30",
                },
                parameter_only=("structure_id", "query"),
            )
        ],
        responses={
            200: OpenApiResponse(
                description=(
                    "ZIP archive (application/zip) containing one C59 XML file "
                    "per calendar day in the requested range."
                )
            ),
            400: OpenApiResponse(description="Validation or business-rule error"),
            403: OpenApiResponse(description="Permission denied"),
            404: OpenApiResponse(description="Structure not found"),
            500: OpenApiResponse(description="Unexpected server error"),
        },
    )
    def get(self, request):
        try:
            params = self._validate_query(request)
            _get_accessible_structure_or_raise(
                user=request.user,
                structure_id=params["structure_id"],
            )
            response = generate_c59_zip_download(**params)
        except serializers.ValidationError as exc:
            return Response(exc.detail, status=status.HTTP_400_BAD_REQUEST)
        except XmlPayloadValidationError as exc:
            logger.warning(
                "C59 ZIP export API failed",
                extra={
                    "user_id": request.user.id,
                    "structure_id": request.query_params.get("structure_id"),
                    "start_date": request.query_params.get("start_date"),
                    "end_date": request.query_params.get("end_date"),
                    "export_type": "c59_zip",
                    "status": "failure",
                },
            )
            return Response({"detail": str(exc)}, status=status.HTTP_400_BAD_REQUEST)
        except PermissionDenied as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_403_FORBIDDEN)
        except Http404 as exc:
            return Response({"detail": str(exc)}, status=status.HTTP_404_NOT_FOUND)
        except Exception:
            logger.exception(
                "C59 ZIP export API unexpected failure",
                extra={
                    "user_id": request.user.id,
                    "structure_id": request.query_params.get("structure_id"),
                    "start_date": request.query_params.get("start_date"),
                    "end_date": request.query_params.get("end_date"),
                    "export_type": "c59_zip",
                    "status": "failure",
                },
            )
            return Response(
                {"detail": "An unexpected error occurred."},
                status=status.HTTP_500_INTERNAL_SERVER_ERROR,
            )

        logger.info(
            "C59 ZIP export API succeeded",
            extra={
                "user_id": request.user.id,
                "structure_id": params["structure_id"],
                "start_date": params["start_date"].isoformat(),
                "end_date": params["end_date"].isoformat(),
                "export_type": "c59_zip",
                "status": "success",
            },
        )
        return response
