import logging

from urllib.parse import parse_qs, urlencode

from django.conf import settings
from django.contrib import messages
from django.core.cache import cache
from django.shortcuts import redirect
from django.urls import reverse_lazy
from django.utils.translation import gettext as _
from django.views.generic import RedirectView, TemplateView

from requests_oauthlib import OAuth1 as OAuth

from newsreader.news.collection.exceptions import StreamException
from newsreader.news.collection.reddit import (
    get_reddit_access_token,
    get_reddit_authorization_url,
    revoke_reddit_token,
)
from newsreader.news.collection.tasks import RedditTokenTask
from newsreader.news.collection.twitter import (
    TWITTER_ACCESS_TOKEN_URL,
    TWITTER_AUTH_URL,
    TWITTER_REQUEST_TOKEN_URL,
    TWITTER_REVOKE_URL,
)
from newsreader.news.collection.utils import post


logger = logging.getLogger(__name__)


class IntegrationsView(TemplateView):
    template_name = "accounts/views/integrations.html"

    def get_context_data(self, **kwargs):
        return {
            **super().get_context_data(**kwargs),
            **self.get_reddit_context(**kwargs),
            **self.get_twitter_context(**kwargs),
        }

    def get_reddit_context(self, **kwargs):
        user = self.request.user
        reddit_authorization_url = None
        reddit_refresh_url = None

        reddit_task_active = cache.get(f"{user.email}-reddit-refresh")

        if (
            user.reddit_refresh_token
            and not user.reddit_access_token
            and not reddit_task_active
        ):
            reddit_refresh_url = reverse_lazy("accounts:settings:reddit-refresh")

        if not user.reddit_refresh_token:
            reddit_authorization_url = get_reddit_authorization_url(user)

        return {
            "reddit_authorization_url": reddit_authorization_url,
            "reddit_refresh_url": reddit_refresh_url,
            "reddit_revoke_url": (
                reverse_lazy("accounts:settings:reddit-revoke")
                if not reddit_authorization_url
                else None
            ),
        }

    def get_twitter_context(self, **kwargs):
        twitter_revoke_url = None

        if self.request.user.has_twitter_auth:
            twitter_revoke_url = reverse_lazy("accounts:settings:twitter-revoke")

        return {
            "twitter_auth_url": reverse_lazy("accounts:settings:twitter-auth"),
            "twitter_revoke_url": twitter_revoke_url,
        }


class RedditTemplateView(TemplateView):
    template_name = "accounts/views/reddit.html"

    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)

        error = request.GET.get("error", None)
        state = request.GET.get("state", None)
        code = request.GET.get("code", None)

        if error:
            return self.render_to_response({**context, "error": error})

        if not code or not state:
            return self.render_to_response(context)

        cached_state = cache.get(f"{request.user.email}-reddit-auth")

        if state != cached_state:
            return self.render_to_response(
                {
                    **context,
                    "error": _(
                        "The saved state for Reddit authorization did not match"
                    ),
                }
            )

        try:
            access_token, refresh_token = get_reddit_access_token(code, request.user)

            return self.render_to_response(
                {
                    **context,
                    "access_token": access_token,
                    "refresh_token": refresh_token,
                }
            )
        except StreamException as e:
            return self.render_to_response({**context, "error": str(e)})
        except KeyError:
            return self.render_to_response(
                {
                    **context,
                    "error": _("Access and refresh token not found in response"),
                }
            )


class RedditTokenRedirectView(RedirectView):
    url = reverse_lazy("accounts:settings:integrations")

    def get(self, request, *args, **kwargs):
        response = super().get(request, *args, **kwargs)

        user = request.user
        task_active = cache.get(f"{user.email}-reddit-refresh")

        if not task_active:
            RedditTokenTask.delay(user.pk)
            messages.success(request, _("Access token is being retrieved"))
            cache.set(f"{user.email}-reddit-refresh", 1, 300)
            return response

        messages.error(request, _("Unable to retrieve token"))
        return response


class RedditRevokeRedirectView(RedirectView):
    url = reverse_lazy("accounts:settings:integrations")

    def get(self, request, *args, **kwargs):
        response = super().get(request, *args, **kwargs)

        user = request.user

        if not user.reddit_refresh_token:
            messages.error(request, _("No reddit account is linked to this account"))
            return response

        try:
            is_revoked = revoke_reddit_token(user)
        except StreamException:
            logger.exception(f"Unable to revoke reddit token for {user.pk}")

            messages.error(request, _("Unable to revoke reddit token"))
            return response

        if not is_revoked:
            messages.error(request, _("Unable to revoke reddit token"))
            return response

        user.reddit_access_token = None
        user.reddit_refresh_token = None
        user.save()

        messages.success(request, _("Reddit account deathorized"))
        return response


class TwitterRevokeRedirectView(RedirectView):
    url = reverse_lazy("accounts:settings:integrations")

    def get(self, request, *args, **kwargs):
        if not request.user.has_twitter_auth:
            messages.error(request, _("No twitter credentials found"))
            return super().get(request, *args, **kwargs)

        oauth = OAuth(
            settings.TWITTER_CONSUMER_ID,
            client_secret=settings.TWITTER_CONSUMER_SECRET,
            resource_owner_key=request.user.twitter_oauth_token,
            resource_owner_secret=request.user.twitter_oauth_token_secret,
        )

        try:
            post(TWITTER_REVOKE_URL, auth=oauth)
        except StreamException:
            logger.exception("Failed revoking Twitter account")

            messages.error(request, _("Unable revoke Twitter account"))
            return super().get(request, *args, **kwargs)

        request.user.twitter_oauth_token = None
        request.user.twitter_oauth_token_secret = None
        request.user.save()

        messages.success(request, _("Twitter account revoked"))
        return super().get(request, *args, **kwargs)


class TwitterAuthRedirectView(RedirectView):
    url = reverse_lazy("accounts:settings:integrations")

    def get(self, request, *args, **kwargs):
        oauth = OAuth(
            settings.TWITTER_CONSUMER_ID,
            client_secret=settings.TWITTER_CONSUMER_SECRET,
            callback_uri=settings.TWITTER_REDIRECT_URL,
        )

        try:
            response = post(TWITTER_REQUEST_TOKEN_URL, auth=oauth)
        except StreamException:
            logger.exception("Failed requesting Twitter authentication token")

            messages.error(request, _("Unable to retrieve initial Twitter token"))
            return super().get(request, *args, **kwargs)

        params = parse_qs(response.text)

        try:
            request_oauth_token = params["oauth_token"][0]
            request_oauth_secret = params["oauth_token_secret"][0]
        except KeyError:
            logger.exception("No credentials found in response")

            messages.error(request, _("Unable to retrieve initial Twitter token"))
            return super().get(request, *args, **kwargs)

        cache.set_many(
            {
                f"twitter-{request.user.email}-token": request_oauth_token,
                f"twitter-{request.user.email}-secret": request_oauth_secret,
            }
        )

        request_params = urlencode({"oauth_token": request_oauth_token})
        return redirect(f"{TWITTER_AUTH_URL}/?{request_params}")


class TwitterTemplateView(TemplateView):
    template_name = "accounts/views/twitter.html"

    def get(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)

        denied = request.GET.get("denied", False)
        oauth_token = request.GET.get("oauth_token")
        oauth_verifier = request.GET.get("oauth_verifier")

        if denied:
            return self.render_to_response(
                {
                    **context,
                    "error": _("Twitter authorization failed"),
                    "authorized": False,
                }
            )

        cached_token = cache.get(f"twitter-{request.user.email}-token")

        if oauth_token != cached_token:
            return self.render_to_response(
                {
                    **context,
                    "error": _("OAuth tokens failed to match"),
                    "authorized": False,
                }
            )

        cached_secret = cache.get(f"twitter-{request.user.email}-secret")

        if not cached_token or not cached_secret:
            return self.render_to_response(
                {
                    **context,
                    "error": _("No matching tokens found for this user"),
                    "authorized": False,
                }
            )

        oauth = OAuth(
            settings.TWITTER_CONSUMER_ID,
            client_secret=settings.TWITTER_CONSUMER_SECRET,
            resource_owner_key=cached_token,
            resource_owner_secret=cached_secret,
            verifier=oauth_verifier,
        )

        try:
            response = post(TWITTER_ACCESS_TOKEN_URL, auth=oauth)
        except StreamException:
            logger.exception("Failed requesting Twitter access token")

            return self.render_to_response(
                {
                    **context,
                    "error": _("Failed requesting access token"),
                    "authorized": False,
                }
            )

        params = parse_qs(response.text)

        try:
            oauth_token = params["oauth_token"][0]
            oauth_secret = params["oauth_token_secret"][0]
        except KeyError:
            logger.exception("No credentials in Twitter response")

            return self.render_to_response(
                {
                    **context,
                    "error": _("No credentials found in Twitter response"),
                    "authorized": False,
                }
            )

        request.user.twitter_oauth_token = oauth_token
        request.user.twitter_oauth_token_secret = oauth_secret
        request.user.save()

        cache.delete_many(
            [
                f"twitter-{request.user.email}-token",
                f"twitter-{request.user.email}-secret",
            ]
        )

        return self.render_to_response({**context, "error": None, "authorized": True})
