from datetime import timedelta

from django.db.models import Count, Q, Sum
from django.db.models.functions import Coalesce
from django.utils import timezone
from rest_framework import status, viewsets
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied, ValidationError
from rest_framework.parsers import MultiPartParser
from rest_framework.response import Response
from drf_spectacular.utils import extend_schema, extend_schema_view, OpenApiParameter

from rest_framework.permissions import IsAuthenticated
from social.hubs.models import Tags
from vontor_cz.pagination import CreatedCursorPagination, TopPostsCursorPagination
from .models import Post, PostContent, PostVote, PostSave
from .permissions import CanDeletePost, IsPostAuthorOnly
from .serializers import PostSerializer, PostContentSerializer, PostVoteSerializer, TagAttachSerializer


# ---------------------------------------------------------------------------
# Post ViewSet
# ---------------------------------------------------------------------------

@extend_schema_view(
    list=extend_schema(
        tags=["posts"],
        summary="List posts",
        description="Returns posts. Filter by `hub` query param to scope to a hub.",
    ),
    retrieve=extend_schema(
        tags=["posts"],
        summary="Retrieve a post",
    ),
    create=extend_schema(
        tags=["posts"],
        summary="Create a post",
        description="Creates a post. The requesting user is set as the author automatically.",
    ),
    update=extend_schema(
        tags=["posts"],
        summary="Replace a post",
        description="Full update. Author only.",
    ),
    partial_update=extend_schema(
        tags=["posts"],
        summary="Update a post",
        description="Partial update. Author only.",
    ),
    destroy=extend_schema(
        tags=["posts"],
        summary="Delete a post",
        description="Soft-deletes the post. Author, superuser, hub owner, or hub moderator with `managing_posts`.",
    ),
)
class PostViewSet(viewsets.ModelViewSet):
    serializer_class = PostSerializer
    filterset_fields = ['hub', 'author', 'reply_to']
    search_fields = ['content']
    ordering_fields = ['created_at']
    ordering = ['-created_at']

    def get_permissions(self):
        if self.action == 'destroy':
            return [CanDeletePost()]
        if self.action in ('update', 'partial_update'):
            return [IsPostAuthorOnly()]
        return [IsAuthenticated()]

    def get_queryset(self):
        qs = (
            Post.objects
            .select_related('author', 'hub')
            .prefetch_related('tags', 'contents', 'votes', 'saves')
            .annotate(reply_count=Count('replies', distinct=True))
        )
        hub_id = self.request.query_params.get('hub')
        if hub_id:
            qs = qs.filter(hub_id=hub_id)
        return qs

    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

    _TIME_WINDOWS = {
        '1h':    timedelta(hours=1),
        '6h':    timedelta(hours=6),
        'day':   timedelta(days=1),
        'week':  timedelta(weeks=1),
        'month': timedelta(days=30),
        'year':  timedelta(days=365),
    }

    def _get_cutoff(self, time_param):
        """Return a datetime cutoff for the given time window, or None for 'all'."""
        if time_param in self._TIME_WINDOWS:
            return timezone.now() - self._TIME_WINDOWS[time_param]
        return None

    def list(self, request, *args, **kwargs):
        sort = request.query_params.get('sort', 'newest')
        time_param = request.query_params.get('time', 'all')

        qs = self.filter_queryset(self.get_queryset())

        # Time filter
        if time_param == 'custom':
            start = request.query_params.get('start')
            end = request.query_params.get('end')
            if start:
                qs = qs.filter(created_at__date__gte=start)
            if end:
                qs = qs.filter(created_at__date__lte=end)
        else:
            cutoff = self._get_cutoff(time_param)
            if cutoff:
                qs = qs.filter(created_at__gte=cutoff)

        if sort == 'top':
            qs = qs.annotate(vote_score=Coalesce(Sum('votes__vote'), 0)).order_by('-vote_score', '-id')
            paginator = TopPostsCursorPagination()
        else:
            qs = qs.order_by('-created_at')
            paginator = CreatedCursorPagination()

        page = paginator.paginate_queryset(qs, request, view=self)
        ser = PostSerializer(page, many=True, context={'request': request})
        return paginator.get_paginated_response(ser.data)

    # ------------------------------------------------------------------
    # Media upload action
    # ------------------------------------------------------------------

    @extend_schema(
        tags=["posts"],
        summary="Upload media to a post",
        description="Attach an image or video file to a post. Only the post author can upload.",
        request={'multipart/form-data': {
            'type': 'object',
            'properties': {'file': {'type': 'string', 'format': 'binary'}},
            'required': ['file'],
        }},
        responses={201: PostContentSerializer},
    )
    @action(detail=True, methods=['post'], url_path='media', parser_classes=[MultiPartParser])
    def upload_media(self, request, pk=None):
        post = self.get_object()
        if post.author != request.user:
            raise PermissionDenied('Only the post author can upload media.')
        file = request.FILES.get('file')
        if not file:
            raise ValidationError({'file': 'No file provided.'})
        content = PostContent(post=post, file=file)
        content.save()
        return Response(PostContentSerializer(content).data, status=status.HTTP_201_CREATED)

    # ------------------------------------------------------------------
    # Tag attachment actions
    # ------------------------------------------------------------------

    @extend_schema(
        tags=["posts"],
        summary="Attach a tag to a post",
        description=(
            "Attaches an existing hub tag to the post. "
            "The tag must belong to the same hub as the post. "
            "Any authenticated hub member can attach tags."
        ),
        request=TagAttachSerializer,
        responses={200: PostSerializer},
    )
    @action(detail=True, methods=['post'], url_path='tags/attach')
    def attach_tag(self, request, pk=None):
        post = self.get_object()
        ser = TagAttachSerializer(data=request.data)
        ser.is_valid(raise_exception=True)

        try:
            tag = Tags.objects.get(pk=ser.validated_data['tag_id'], hub=post.hub)
        except Tags.DoesNotExist:
            raise ValidationError({'tag_id': 'Tag not found or does not belong to this post\'s hub.'})

        post.tags.add(tag)
        return Response(PostSerializer(post, context={'request': request}).data)

    @extend_schema(
        tags=["posts"],
        summary="Detach a tag from a post",
        description=(
            "Removes a tag from the post. "
            "Post author, hub owner, site admin, or moderator with `managing_posts` can detach."
        ),
        request=TagAttachSerializer,
        responses={200: PostSerializer},
    )
    @action(detail=True, methods=['post'], url_path='tags/detach')
    def detach_tag(self, request, pk=None):
        post = self.get_object()
        ser = TagAttachSerializer(data=request.data)
        ser.is_valid(raise_exception=True)

        user = request.user
        hub = post.hub
        is_author = post.author == user
        is_hub_owner = hub and hub.owner == user
        is_moderator = hub and hub.moderators.filter(user=user, managing_posts=True).exists()

        if not (is_author or user.is_superuser or is_hub_owner or is_moderator):
            raise PermissionDenied('Only the post author, hub owner, admin, or moderator with managing_posts can detach tags.')

        try:
            tag = Tags.objects.get(pk=ser.validated_data['tag_id'], hub=post.hub)
        except Tags.DoesNotExist:
            raise ValidationError({'tag_id': 'Tag not found or does not belong to this post\'s hub.'})

        post.tags.remove(tag)
        return Response(PostSerializer(post, context={'request': request}).data)

    # ------------------------------------------------------------------
    # Feed action (cursor-paginated aggregated feed for the user)
    # ------------------------------------------------------------------

    @extend_schema(
        tags=["posts"],
        summary="Get the user's post feed",
        description=(
            "Returns a cursor-paginated stream of top-level posts (excluding replies) "
            "aggregated from the user's joined hubs, public hubs, and hub-less posts. "
            "Pass `feed_strategy` to switch between ranking algorithms (currently only "
            "`recent` is implemented; reserved for future custom algorithms)."
        ),
        parameters=[
            OpenApiParameter(name='feed_strategy', required=False, type=str,
                             description="Algorithm key, default `recent`."),
            OpenApiParameter(name='cursor', required=False, type=str,
                             description="Opaque pagination cursor."),
        ],
        responses={200: PostSerializer(many=True)},
    )
    @action(detail=False, methods=['get'], url_path='feed')
    def feed(self, request):
        user = request.user
        strategy = request.query_params.get('feed_strategy', 'recent')

        base_qs = (
            Post.objects
            .select_related('author', 'hub')
            .prefetch_related('tags', 'contents', 'votes', 'saves')
            .annotate(reply_count=Count('replies', distinct=True))
            .filter(reply_to__isnull=True)
        )

        joined_hub_ids = list(user.hubs.values_list('id', flat=True)) if user.is_authenticated else []
        visibility_filter = (
            Q(hub__isnull=True)
            | Q(hub__is_public=True)
            | Q(hub_id__in=joined_hub_ids)
        )
        qs = base_qs.filter(visibility_filter).distinct()

        if strategy == 'recent':
            qs = qs.order_by('-created_at')
        else:
            qs = qs.order_by('-created_at')

        paginator = CreatedCursorPagination()
        page = paginator.paginate_queryset(qs, request, view=self)
        ser = PostSerializer(page, many=True, context={'request': request})
        return paginator.get_paginated_response(ser.data)

    # ------------------------------------------------------------------
    # Vote action
    # ------------------------------------------------------------------

    @extend_schema(
        tags=["posts"],
        summary="Vote on a post",
        description="Cast or update a vote (`1` = upvote, `-1` = downvote) on a post.",
        request=PostVoteSerializer,
        responses={200: PostVoteSerializer},
    )
    @action(detail=True, methods=['post'])
    def vote(self, request, pk=None):
        post = self.get_object()
        ser = PostVoteSerializer(data={**request.data, 'post': post.pk, 'user': request.user.pk})
        ser.is_valid(raise_exception=True)
        vote_obj, _ = PostVote.objects.update_or_create(
            post=post,
            user=request.user,
            defaults={'vote': ser.validated_data['vote']},
        )
        return Response(PostVoteSerializer(vote_obj).data)

    # ------------------------------------------------------------------
    # Save / unsave action
    # ------------------------------------------------------------------

    @extend_schema(
        tags=["posts"],
        summary="Toggle save on a post",
        description="Saves the post for the current user, or unsaves it if already saved. Returns `{saved: true/false}`.",
        responses={200: {'type': 'object', 'properties': {'saved': {'type': 'boolean'}}}},
    )
    @action(detail=True, methods=['post'], url_path='save')
    def save_post(self, request, pk=None):
        post = self.get_object()
        obj, created = PostSave.objects.get_or_create(post=post, user=request.user)
        if not created:
            obj.delete()
        return Response({'saved': created})

    # ------------------------------------------------------------------
    # Saved feed
    # ------------------------------------------------------------------

    @extend_schema(
        tags=["posts"],
        summary="List posts saved by the current user",
        responses={200: PostSerializer(many=True)},
    )
    @action(detail=False, methods=['get'], url_path='saved')
    def saved(self, request):
        qs = (
            Post.objects
            .select_related('author', 'hub')
            .prefetch_related('tags', 'contents', 'votes', 'saves')
            .annotate(reply_count=Count('replies', distinct=True))
            .filter(saves__user=request.user)
            .order_by('-saves__created_at')
        )
        paginator = CreatedCursorPagination()
        page = paginator.paginate_queryset(qs, request, view=self)
        ser = PostSerializer(page, many=True, context={'request': request})
        return paginator.get_paginated_response(ser.data)
