from django.db.models import Avg, Max, Count, Q, Sum
from rest_framework import viewsets
from rest_framework.decorators import action
from rest_framework.pagination import PageNumberPagination
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, OpenApiParameter

from .models import Publication, PublicationCategory, Author, Submission, ExternalPublication, CSISPublication
from .serializers import (
    PublicationSerializer, PublicationCategorySerializer, AuthorSerializer,
    SubmissionSerializer, ExternalPublicationSerializer, CSISPublicationSerializer,
)


class CSISPublicationPagination(PageNumberPagination):
    page_size = 50
    page_size_query_param = "page_size"
    max_page_size = 500


class PublicationViewSet(viewsets.ModelViewSet):
    """ViewSet for Publication management."""
    rbac_domain = "publications"
    queryset = (
        Publication.objects
        .select_related("category", "author", "byline_contact")
        .prefetch_related("co_author_contacts")
    )
    serializer_class = PublicationSerializer

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="category", type=str, description="Filter by category ID"),
            OpenApiParameter(name="author", type=str, description="Filter by author ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class PublicationCategoryViewSet(viewsets.ModelViewSet):
    """ViewSet for PublicationCategory management."""
    rbac_domain = "publications"
    queryset = PublicationCategory.objects.all()
    serializer_class = PublicationCategorySerializer


class AuthorViewSet(viewsets.ModelViewSet):
    """ViewSet for Author management."""
    rbac_domain = "publications"
    queryset = Author.objects.select_related("user").prefetch_related("publications")
    serializer_class = AuthorSerializer


class SubmissionViewSet(viewsets.ModelViewSet):
    """ViewSet for Submission management."""
    rbac_domain = "publications"
    queryset = Submission.objects.select_related("submitted_by", "reviewed_by")
    serializer_class = SubmissionSerializer

    @extend_schema(
        parameters=[
            OpenApiParameter(name="status", type=str, description="Filter by status"),
            OpenApiParameter(name="publication", type=str, description="Filter by publication ID"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)


class ExternalPublicationViewSet(viewsets.ModelViewSet):
    """ViewSet for ExternalPublication management."""
    rbac_domain = "publications"
    queryset = ExternalPublication.objects.prefetch_related("byline_contacts")
    serializer_class = ExternalPublicationSerializer


class CSISPublicationViewSet(viewsets.ReadOnlyModelViewSet):
    """Read-only access to publications synced from api.csis.or.id."""
    rbac_domain = "publications"
    queryset = CSISPublication.objects.all()
    serializer_class = CSISPublicationSerializer
    pagination_class = CSISPublicationPagination

    def get_queryset(self):
        qs = super().get_queryset()
        params = self.request.query_params
        search = params.get("search")
        category = params.get("category")
        if search:
            qs = qs.filter(Q(title__icontains=search) | Q(description__icontains=search))
        if category:
            qs = qs.filter(Q(category_slug=category) | Q(category_name__iexact=category))
        return qs

    @extend_schema(
        parameters=[
            OpenApiParameter(name="search", type=str, description="Filter by title/description"),
            OpenApiParameter(name="category", type=str, description="Filter by category slug or name"),
            OpenApiParameter(name="page_size", type=int, description="Items per page (max 500)"),
        ]
    )
    def list(self, request, *args, **kwargs):
        return super().list(request, *args, **kwargs)

    @action(detail=False, methods=["get"])
    def stats(self, request):
        from collections import defaultdict
        qs = CSISPublication.objects.all()
        overall = qs.aggregate(
            total=Count("id"),
            total_views=Sum("viewed"),
            total_downloads=Sum("download_count"),
            avg_views=Avg("viewed"),
            avg_downloads=Avg("download_count"),
            max_views=Max("viewed"),
            max_downloads=Max("download_count"),
        )
        by_category = list(
            qs.values("category_name")
              .annotate(
                  count=Count("id"),
                  total_views=Sum("viewed"),
                  total_downloads=Sum("download_count"),
                  avg_views=Avg("viewed"),
                  avg_downloads=Avg("download_count"),
                  max_views=Max("viewed"),
              )
              .order_by("-count")
        )
        top_published = list(
            qs.order_by("-viewed")
              .values("id", "title", "slug", "category_name", "viewed", "download_count")[:10]
        )

        # Aggregate authors (stored in JSON `authors` field per pub).
        author_pub_count: dict[str, int] = defaultdict(int)
        author_views: dict[str, int] = defaultdict(int)
        author_meta: dict[str, dict] = {}
        for row in qs.values_list("authors", "viewed"):
            authors, views = row
            if not authors:
                continue
            for a in authors:
                name = (a or {}).get("name")
                if not name:
                    continue
                author_pub_count[name] += 1
                author_views[name] += views or 0
                if name not in author_meta:
                    author_meta[name] = {
                        "name": name,
                        "slug": (a or {}).get("slug", ""),
                        "profile_img": (a or {}).get("profile_img", ""),
                    }
        top_authors = sorted(
            (
                {
                    **author_meta[n],
                    "publications": author_pub_count[n],
                    "views": author_views[n],
                }
                for n in author_pub_count
            ),
            key=lambda r: (r["views"], r["publications"]),
            reverse=True,
        )[:10]

        # Monthly publication counts from date_publish.
        monthly_buckets: dict[str, dict] = {}
        for date_publish, viewed, downloads in qs.exclude(date_publish__isnull=True).values_list("date_publish", "viewed", "download_count"):
            key = date_publish.strftime("%Y-%m")
            bucket = monthly_buckets.setdefault(key, {"month": key, "count": 0, "views": 0, "downloads": 0})
            bucket["count"] += 1
            bucket["views"] += viewed or 0
            bucket["downloads"] += downloads or 0
        monthly = sorted(monthly_buckets.values(), key=lambda r: r["month"])[-12:]
        return Response({
            "overall": {
                "total": overall["total"] or 0,
                "total_views": overall["total_views"] or 0,
                "total_downloads": overall["total_downloads"] or 0,
                "avg_views": float(overall["avg_views"] or 0),
                "avg_downloads": float(overall["avg_downloads"] or 0),
                "max_views": overall["max_views"] or 0,
                "max_downloads": overall["max_downloads"] or 0,
            },
            "by_category": [
                {
                    "category_name": row["category_name"],
                    "count": row["count"],
                    "total_views": row["total_views"] or 0,
                    "total_downloads": row["total_downloads"] or 0,
                    "avg_views": float(row["avg_views"] or 0),
                    "avg_downloads": float(row["avg_downloads"] or 0),
                    "max_views": row["max_views"] or 0,
                }
                for row in by_category
            ],
            "top_publications": top_published,
            "top_authors": top_authors,
            "monthly": monthly,
        })
