import logging
# Create your views here.
from typing import Optional, List, Dict

from django.db.models import QuerySet, Count
from rest_framework import status
from rest_framework.authentication import TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.views import APIView

from ..models import Account, Statement, Hashtag, HashtagTagging
from ..serializers.contentSerializers import StatementObservationSerializer, StatementSerializer, TrendingHashtagSerializer

logger = logging.getLogger(__name__)


class ShowStatement(APIView):
    """
    This view can be used to get an specific statement and all correlated data by the statements id.
    To get information about an statement one must provide an valid token for identification.
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = (IsAuthenticated,)

    @staticmethod
    def get(request: Request, *args, **kwargs):
        """
        This method returns all information about an specific statement.
        :param request: Not used.
        :param args: Not used.
        :param kwargs: Additional information to get the id of the requested statement.
        :return:
        """
        statement: Statement = Statement.objects.filter(id=int(kwargs.get("id")))
        serializer: StatementObservationSerializer = StatementObservationSerializer(instance=statement, many=True)
        return Response(status=status.HTTP_200_OK, data=serializer.data)


class ShowStatementsWithHashtag(APIView):
    """
    This view is representative for the hashtag view.
    It can be used to get all statements with containing an specific hashtag.
    To get the information one must have an valid token.
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = (IsAuthenticated,)

    @staticmethod
    def get(request: Request):
        """
        This method returns all statements containing the given hashtag string.
        If there is no data or if there is no hashtag one get 200.
        If there is data one get data and 200.
        If the request is wrong one get 400.

        :param request: Request with the parameter q, which is the string representation of the hashtag.
        :return: 200 if there are statements with the hashtag or if there is no data, 400 if the request is invalid.
        """
        query: str = request.query_params.get('q', None)
        if not query:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        hashtag: Optional[Hashtag] = Hashtag.objects.filter(tag=query).first()
        if not hashtag:
            return Response(status=status.HTTP_200_OK)
        statement: List[Statement] = Statement.objects.filter(tagged=hashtag)
        serializer: StatementSerializer = StatementSerializer(instance=statement, many=True)
        return Response(status=status.HTTP_200_OK, data=serializer.data)


class ShowStatementFeed(APIView):
    """
    This view is for querying the feed for an calling account.
    The feed is generated by the accounts the calling account is following.
    To get the feed the calling account must be authenticated.
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = (IsAuthenticated,)

    @staticmethod
    def get(request: Request):
        """
        This is for getting the feed.
        :param request: The request containing the token to identify the calling user.
        :return: Feed for the calling user based on the actions of those the calling account follows.
        """
        account: Account = Account.objects.get(user=request.user)
        following: List[Account] = account.get_related_to() + [account]
        feed: QuerySet[Statement] = Statement.objects.filter(author__in=following)
        serializer: StatementSerializer = StatementSerializer(instance=feed, many=True)
        return Response(status=status.HTTP_200_OK, data=serializer.data)
    
    
class ShowStatementFeedPagination(APIView):
    """
    This view is for querying the feed for an calling account.
    The feed is generated by the accounts the calling account is following.
    To get the feed the calling account must be authenticated.
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = (IsAuthenticated,)

    @staticmethod
    def get(request: Request):
        """
        This is for getting the feed with pagination.
        Todo: Add pagination for infinite scrolling.
        :param request: The request containing the token to identify the calling user. The page number. The size number.
        :return: Feed for the calling user based on the actions of those the calling account follows.
        """
        page: int = int(request.query_params.get('page', None))
        size: int = int(request.query_params.get('size', None))
        if not page or not size or page <= 0 or size <= 0:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        
        account: Account = Account.objects.get(user=request.user)
        following: List[Account] = account.get_related_to() + [account]
        feed: QuerySet[Statement] = Statement.objects.filter(author__in=following)
        
        first = (page - 1) * size
        last = first + size
        total: int = feed.count()
        
        if first >= total:
            feed = []
        else:
            if last >= total:
                last = None
            feed = feed[first:last]
        
        serializer: StatementSerializer = StatementSerializer(instance=feed, many=True)
        return Response(status=status.HTTP_200_OK, data={"data": serializer.data, "total": total})


class ShowTrendingHashtag(APIView):
    """
    This view is for getting the five most trending hashtags.
    The calling user must be authenticated.
    Also the calling user is not included as an participant of the hashtag,
    since those others are for recommendation.
    """
    authentication_classes = [TokenAuthentication]
    permission_classes = (IsAuthenticated,)

    @staticmethod
    def get(request: Request):
        """
        This method handles the request for trending hashtags.
        Therefore the tagging of hashtags are counted and turned into an trending hashtag representation.
        The TrendingHashtagSerializer adds all needed information like the count of uses and other participants.
        The calling account is excluded from the participants.
        :param request: Request containing the the token for identification.
        :return: 200 OK with empty or not empty data section. The data section is empty if there are not hashtags.
        """
        hashtags: QuerySet[Dict] = HashtagTagging.objects.values('hashtag')
        hashtags_counted: QuerySet[Dict] = hashtags.annotate(the_count=Count('hashtag')).order_by("-the_count")
        counted: Dict = {item["hashtag"]: item["the_count"] for item in hashtags_counted}
        hashtags: QuerySet[Hashtag] = Hashtag.objects.filter(id__in=counted.keys())
        if not hashtags_counted:
            return Response(status=status.HTTP_200_OK, data=[])
        serializer: TrendingHashtagSerializer = TrendingHashtagSerializer(
            instance=hashtags,
            many=True,
            context={"counted": counted, "calling_user": request.user.id})
        return Response(status=status.HTTP_200_OK, data=serializer.data[:3])
