"""
Middleware for Mobile APIs
"""
from datetime import datetime

from django.conf import settings
from django.core.cache import cache
from django.http import HttpResponse
from pytz import UTC

import request_cache
from mobile_api.mobile_platform import MobilePlatform
from mobile_api.models import AppVersionConfig
from mobile_api.utils import parsed_version
from openedx.core.lib.mobile_utils import is_request_from_mobile_app


class AppVersionUpgrade(object):
    """
    Middleware class to keep track of mobile application version being used.
    """
    LATEST_VERSION_HEADER = 'EDX-APP-LATEST-VERSION'
    LAST_SUPPORTED_DATE_HEADER = 'EDX-APP-VERSION-LAST-SUPPORTED-DATE'
    NO_LAST_SUPPORTED_DATE = 'NO_LAST_SUPPORTED_DATE'
    NO_LATEST_VERSION = 'NO_LATEST_VERSION'
    USER_APP_VERSION = 'USER_APP_VERSION'
    REQUEST_CACHE_NAME = 'app-version-info'
    CACHE_TIMEOUT = settings.APP_UPGRADE_CACHE_TIMEOUT

    def process_request(self, request):
        """
        Processes request to validate app version that is making request.

        Returns:
            Http response with status code 426 (i.e. Update Required) if request is from
            mobile native app and app version is no longer supported else returns None
        """
        version_data = self._get_version_info(request)
        if version_data:
            last_supported_date = version_data[self.LAST_SUPPORTED_DATE_HEADER]
            if last_supported_date != self.NO_LAST_SUPPORTED_DATE:
                if datetime.now().replace(tzinfo=UTC) > last_supported_date:
                    return HttpResponse(status=426)  # Http status 426; Update Required

    def process_response(self, __, response):
        """
        If request is from mobile native app, then add version related info to response headers.

        Returns:
            Http response: with additional headers;
                1. EDX-APP-LATEST-VERSION; if user app version < latest available version
                2. EDX-APP-VERSION-LAST-SUPPORTED-DATE; if user app version < min supported version and
                   timestamp < expiry of that version
        """
        request_cache_dict = request_cache.get_cache(self.REQUEST_CACHE_NAME)
        if request_cache_dict:
            last_supported_date = request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER]
            if last_supported_date != self.NO_LAST_SUPPORTED_DATE:
                response[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date.isoformat()
            latest_version = request_cache_dict[self.LATEST_VERSION_HEADER]
            user_app_version = request_cache_dict[self.USER_APP_VERSION]
            if (latest_version != self.NO_LATEST_VERSION and
                    parsed_version(user_app_version) < parsed_version(latest_version)):
                response[self.LATEST_VERSION_HEADER] = latest_version
        return response

    def _get_cache_key_name(self, field, key):
        """
        Get key name to use to cache any property against field name and identification key.

        Arguments:
            field (str): The property name that needs to get cached.
            key (str): Unique identification for cache key (e.g. platform_name).

        Returns:
            string: Cache key to be used.
        """
        return "mobile_api.app_version_upgrade.{}.{}".format(field, key)

    def _get_version_info(self, request):
        """
        Gets and Sets version related info in mem cache and request cache; and returns a dict of it.

        It sets request cache data for last_supported_date and latest_version with memcached values if exists against
        user app properties else computes the values for specific platform and sets it in both memcache (for next
        server interaction from same app version/platform) and request cache

        Returns:
            dict: Containing app version info
        """
        user_agent = request.META.get('HTTP_USER_AGENT')
        if user_agent:
            platform = self._get_platform(request, user_agent)
            if platform:
                request_cache_dict = request_cache.get_cache(self.REQUEST_CACHE_NAME)
                request_cache_dict[self.USER_APP_VERSION] = platform.version
                last_supported_date_cache_key = self._get_cache_key_name(
                    self.LAST_SUPPORTED_DATE_HEADER,
                    platform.version
                )
                latest_version_cache_key = self._get_cache_key_name(self.LATEST_VERSION_HEADER, platform.NAME)
                cached_data = cache.get_many([last_supported_date_cache_key, latest_version_cache_key])

                last_supported_date = cached_data.get(last_supported_date_cache_key)
                if not last_supported_date:
                    last_supported_date = self._get_last_supported_date(platform.NAME, platform.version)
                    cache.set(last_supported_date_cache_key, last_supported_date, self.CACHE_TIMEOUT)
                request_cache_dict[self.LAST_SUPPORTED_DATE_HEADER] = last_supported_date

                latest_version = cached_data.get(latest_version_cache_key)
                if not latest_version:
                    latest_version = self._get_latest_version(platform.NAME)
                    cache.set(latest_version_cache_key, latest_version, self.CACHE_TIMEOUT)
                request_cache_dict[self.LATEST_VERSION_HEADER] = latest_version

                return request_cache_dict

    def _get_platform(self, request, user_agent):
        """
        Determines the platform type for mobile app making the request against user_agent.

        Returns:
            None if request app does not belong to one of the supported mobile platforms
            else returns an instance of corresponding mobile platform.
        """
        if is_request_from_mobile_app(request):
            return MobilePlatform.get_instance(user_agent)

    def _get_last_supported_date(self, platform_name, platform_version):
        """ Get expiry date of app version for a platform. """
        return AppVersionConfig.last_supported_date(platform_name, platform_version) or self.NO_LAST_SUPPORTED_DATE

    def _get_latest_version(self, platform_name):
        """ Get latest app version available for platform. """
        return AppVersionConfig.latest_version(platform_name) or self.NO_LATEST_VERSION
