import json
import logging

from django.utils import timezone
from django.views.decorators.csrf import csrf_exempt
from django.utils.decorators import method_decorator
from profiles.models import UserProfile
from rest_framework import status
from rest_framework.authentication import BaseAuthentication, TokenAuthentication
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView
from scrobbles.scrobblers import (
    emacs_scrobble_task,
    emacs_scrobble_update_task,
    todoist_scrobble_task,
    todoist_scrobble_update_task,
)

logger = logging.getLogger(__name__)


class TodoistTokenAuthentication(BaseAuthentication):
    keyword = "Bearer"

    def authenticate(self, request):
        auth_header = request.META.get("HTTP_AUTHORIZATION", "")

        logger.info(
            "[todoist_auth] Raw auth header: %s",
            auth_header[:20] if auth_header else "None",
        )

        if not auth_header:
            return None

        parts = auth_header.split()

        if len(parts) != 2 or parts[0] != self.keyword:
            logger.warning(
                "[todoist_auth] Invalid auth header format: parts=%s",
                parts,
            )
            return None

        token = parts[1]

        profile = UserProfile.objects.filter(todoist_auth_key=token).first()

        if not profile:
            logger.warning("[todoist_auth] No profile found for token")
            raise AuthenticationFailed("Invalid token")

        if not profile.user:
            logger.warning("[todoist_auth] No user associated with profile")
            raise AuthenticationFailed("No user associated with token")

        logger.info("[todoist_auth] Authenticated user: %s", profile.user.id)
        return (profile.user, profile)

    def authenticate_header(self, request):
        return self.keyword


class TodoistWebhookView(APIView):
    authentication_classes = []
    permission_classes = []

    @method_decorator(csrf_exempt)
    def dispatch(self, request, *args, **kwargs):
        return super().dispatch(request, *args, **kwargs)

    @property
    def logger(self):
        return logger

    def post(self, request):
        logger.info(
            "[todoist_webhook] Raw auth header: %s",
            request.META.get("HTTP_AUTHORIZATION", "None"),
        )

        user_profile = UserProfile.objects.filter(
            todoist_user_id=request.data.get("user_id", None)
        ).first()

        if not user_profile:
            logger.warning(
                "[todoist_webhook] No user profile found for user_id: %s",
                request.data.get("user_id"),
            )
            return Response(
                {"error": "No user found for this webhook"},
                status=status.HTTP_401_UNAUTHORIZED,
            )

        post_data = request.data
        self.logger.info(
            "[todoist_webhook] called",
            extra={"post_data": post_data},
        )
        todoist_task = {}
        todoist_note = {}
        todoist_type, todoist_event = post_data.get("event_name").split(":")
        event_data = post_data.get("event_data", {})
        is_item_type = todoist_type == "item"
        is_note_type = todoist_type == "note"
        new_labels = event_data.get("labels", [])
        old_labels = (
            post_data.get("event_data_extra", {}).get("old_item", {}).get("labels", [])
        )
        is_updated = todoist_event in ["updated"]
        is_added = todoist_event in ["added"]

        task_started = "inprogress" in new_labels and "inprogress" not in old_labels
        task_stopped = "inprogress" not in new_labels and "inprogress" in old_labels

        if is_item_type and is_updated and (task_started or task_stopped):
            todoist_task = {
                "todoist_id": event_data.get("id"),
                "todoist_label_list": event_data.get("labels"),
                "todoist_type": todoist_type,
                "todoist_event": todoist_event,
                "updated_at": event_data.get("updated_at"),
                "todoist_project_id": event_data.get("project_id"),
                "description": event_data.get("content"),
                "details": event_data.get("description"),
            }
        if is_note_type and is_added:
            task_data = event_data.get("item", {})
            todoist_note = {
                "task_id": event_data.get("item_id"),
                "todoist_id": event_data.get("id"),
                "todoist_label_list": task_data.get("labels"),
                "todoist_type": todoist_type,
                "todoist_event": todoist_event,
                "updated_at": task_data.get("updated_at"),
                "posted_at": event_data.get("posted_at"),
                "details": task_data.get("description"),
                "notes": event_data.get("content"),
                "is_deleted": (
                    True if event_data.get("is_deleted") == "true" else False
                ),
            }

        if (is_added and not todoist_note) or (is_updated and not todoist_task):
            self.logger.info(
                "[todoist_webhook] ignoring wrong todoist type, event or labels",
                extra={
                    "todoist_type": todoist_type,
                    "todoist_event": todoist_event,
                    "task_started": task_started,
                    "task_stopped": task_stopped,
                    "new_labels": new_labels,
                    "old_labels": old_labels,
                },
            )
            return Response({}, status=status.HTTP_304_NOT_MODIFIED)

        scrobble = None
        if todoist_task:
            scrobble = todoist_scrobble_task(
                todoist_task,
                user_profile.user_id,
                stopped=task_stopped,
                user_context_list=user_profile.task_context_tags,
            )

        if todoist_note:
            scrobble = todoist_scrobble_update_task(todoist_note, user_profile.user_id)

        if not scrobble:
            self.logger.info(
                "[todoist_webhook] finished with no note or task found",
                extra={"scrobble_id": None},
            )
            return Response(
                {"error": "No scrobble found to be updated"},
                status=status.HTTP_304_NOT_MODIFIED,
            )

        self.logger.info(
            "[todoist_webhook] finished",
            extra={"scrobble_id": scrobble.id},
        )
        return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)


class EmacsWebhookView(APIView):
    authentication_classes = [TokenAuthentication]
    permission_classes = [IsAuthenticated]

    @property
    def logger(self):
        return logger

    def post(self, request):
        try:
            post_data = json.loads(request.data)
        except TypeError:
            post_data = request.data
        self.logger.info(
            "[emacs_webhook] called",
            extra={"post_data": post_data},
        )
        task_in_progress = post_data.get("state") == "STRT"
        task_stopped = post_data.get("state") == "DONE"
        post_data["source_id"] = post_data.pop("emacs_id")

        user_id = request.user.id

        user_profile = UserProfile.objects.filter(user_id=user_id).first()

        scrobble = None
        if post_data.get("source_id"):
            scrobble = emacs_scrobble_task(
                post_data,
                user_id,
                started=task_in_progress,
                stopped=task_stopped,
                user_context_list=user_profile.task_context_tags,
            )

        if not scrobble:
            self.logger.info(
                "[emacs_webhook] finished with no note or task found",
                extra={"scrobble_id": None},
            )
            return Response(
                {"error": "No scrobble found to be updated"},
                status=status.HTTP_304_NOT_MODIFIED,
            )

        if scrobble and scrobble.in_progress:
            emacs_scrobble_update_task(
                post_data.get("source_id"),
                post_data.get("notes") or [],
                user_id,
                description=post_data.get("body"),
            )

        self.logger.info(
            "[emacs_webhook] finished",
            extra={"scrobble_id": scrobble.id},
        )
        return Response({"scrobble_id": scrobble.id}, status=status.HTTP_200_OK)
