import logging
import random
import string
import requests
import json
from constance import config

from rest_framework import status
from rest_framework.authentication import SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from seahub.api2.authentication import TokenAuthentication
from seahub.api2.throttling import UserRateThrottle
from seahub.api2.utils import api_error

from seaserv import seafile_api

from seahub.utils.repo import get_available_repo_perms, get_repo_owner
from seahub.base.templatetags.seahub_tags import email2nickname
from seahub.constants import PERMISSION_READ, PERMISSION_READ_WRITE
from seahub.ocm.models import OCMShareReceived, OCMShare
from seahub.ocm.settings import ENABLE_OCM, SUPPORTED_OCM_PROTOCOLS, \
    OCM_SEAFILE_PROTOCOL, OCM_RESOURCE_TYPE_LIBRARY, OCM_API_VERSION, \
    OCM_SHARE_TYPES, OCM_ENDPOINT, OCM_PROVIDER_ID, OCM_NOTIFICATION_TYPE_LIST, \
    OCM_NOTIFICATION_SHARE_UNSHARED, OCM_NOTIFICATION_SHARE_DECLINED, OCM_PROTOCOL_URL, \
    OCM_NOTIFICATION_URL, OCM_CREATE_SHARE_URL, OCM_REMOTE_SERVERS

logger = logging.getLogger(__name__)

# Convert seafile permission to ocm protocol standard permission
SEAFILE_PERMISSION2OCM_PERMISSION = {
    PERMISSION_READ: ['read'],
    PERMISSION_READ_WRITE: ['read', 'write'],
}


def get_server_name_by_url(url):
    for name_domain_dict in OCM_REMOTE_SERVERS:
        if name_domain_dict['server_url'] == url:
            return name_domain_dict['server_name']


def gen_shared_secret(length=23):
    return ''.join(random.choice(string.ascii_lowercase + string.digits) for i in range(length))


def get_remote_protocol(url):
    response = requests.get(url)
    return json.loads(response.text)


def is_valid_url(url):
    if not url.startswith('https://') and not url.startswith('http://'):
        return False
    if not url.endswith('/'):
        return False
    return True


def check_url_slash(url):
    if not url.endswith('/'):
        url += '/'
    return url


class OCMProtocolView(APIView):
    throttle_classes = (UserRateThrottle,)

    def get(self, request):
        """
        return ocm protocol info to remote server
        """
        # TODO
        # currently if ENABLE_OCM is False, return 404 as if ocm protocol is not implemented
        # ocm protocol is not clear about this, https://github.com/GEANT/OCM-API/pull/37
        if not ENABLE_OCM:
            error_msg = 'feature not enabled.'
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        result = {
            'enabled': True,
            'apiVersion': OCM_API_VERSION,
            'endPoint': config.SERVICE_URL + '/' + OCM_ENDPOINT,
            'resourceTypes': {
                'name': OCM_RESOURCE_TYPE_LIBRARY,
                'shareTypes': OCM_SHARE_TYPES,
                'protocols': {
                    OCM_SEAFILE_PROTOCOL: OCM_SEAFILE_PROTOCOL,
                }
            }
        }
        return Response(result)


class OCMSharesView(APIView):
    throttle_classes = (UserRateThrottle,)

    def post(self, request):
        """
        create ocm in consumer server
        """

        # argument check
        share_with = request.data.get('shareWith', '')
        if not share_with:
            error_msg = 'shareWith invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        # curently only support repo share
        repo_name = request.data.get('name', '')
        if not repo_name:
            error_msg = 'name invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        sender = request.data.get('sender', '')
        if not sender:
            error_msg = 'sender invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        share_type = request.data.get('shareType', '')
        if share_type not in OCM_SHARE_TYPES:
            error_msg = 'shareType %s invalid.' % share_type
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        resource_type = request.data.get('resourceType', '')
        if resource_type != OCM_RESOURCE_TYPE_LIBRARY:
            error_msg = 'resourceType %s invalid.' % resource_type
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        provider_id = request.data.get('providerId', '')
        if not provider_id:
            error_msg = 'providerId invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        """
        other ocm protocol fields currently not used

        description = request.data.get('description', '')
        owner = request.data.get('owner', '')
        ownerDisplayName = request.data.get('ownerDisplayName', '')
        senderDisplayName = request.data.get('senderDisplayName', '')
        """

        protocol = request.data.get('protocol', '')
        if not protocol:
            error_msg = 'protocol invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if 'name' not in protocol.keys():
            error_msg = 'protocol.name invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if protocol['name'] not in SUPPORTED_OCM_PROTOCOLS:
            error_msg = 'protocol %s not support.' % protocol['name']
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if 'options' not in protocol.keys():
            error_msg = 'protocol.options invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if 'sharedSecret' not in protocol['options'].keys():
            error_msg = 'protocol.options.sharedSecret invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if 'permissions' not in protocol['options'].keys():
            error_msg = 'protocol.options.permissions invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if protocol['name'] == OCM_SEAFILE_PROTOCOL:
            if 'repoId' not in protocol['options'].keys():
                error_msg = 'protocol.options.repoId invalid.'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

            if 'seafileServiceURL' not in protocol['options'].keys():
                error_msg = 'protocol.options.seafileServiceURL invalid.'
                return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if protocol['name'] == OCM_SEAFILE_PROTOCOL:
            shared_secret = protocol['options']['sharedSecret']
            permissions = protocol['options']['permissions']
            repo_id = protocol['options']['repoId']
            from_server_url = protocol['options']['seafileServiceURL']

        if OCMShareReceived.objects.filter(
            from_user=sender,
            to_user=share_with,
            from_server_url=from_server_url,
            repo_id=repo_id,
            repo_name=repo_name,
            provider_id=provider_id,
        ).exists():
            return api_error(status.HTTP_400_BAD_REQUEST, 'same share already exists.')

        if 'write' in permissions:
            permission = PERMISSION_READ_WRITE
        else:
            permission = PERMISSION_READ

        OCMShareReceived.objects.add(
            shared_secret=shared_secret,
            from_user=sender,
            to_user=share_with,
            from_server_url=from_server_url,
            repo_id=repo_id,
            repo_name=repo_name,
            permission=permission,
            provider_id=provider_id,
        )

        return Response(request.data, status=status.HTTP_201_CREATED)


class OCMNotificationsView(APIView):
    throttle_classes = (UserRateThrottle,)

    def post(self, request):
        """ Handle notifications from remote server
        """
        notification_type = request.data.get('notificationType', '')
        if not notification_type:
            error_msg = 'notificationType %s invalid.' % notification_type
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if notification_type not in OCM_NOTIFICATION_TYPE_LIST:
            error_msg = 'notificationType %s not supportd.' % notification_type
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        resource_type = request.data.get('resourceType', '')
        if resource_type != OCM_RESOURCE_TYPE_LIBRARY:
            error_msg = 'resourceType %s invalid.' % resource_type
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        notification = request.data.get('notification', '')
        if not notification:
            error_msg = 'notification invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        shared_secret = notification.get('sharedSecret', '')
        if not shared_secret:
            error_msg = 'sharedSecret invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        if notification_type == OCM_NOTIFICATION_SHARE_UNSHARED:
            """
            Provider unshared, then delete ocm_share_received record on Consumer
            """
            try:
                ocm_share_received = OCMShareReceived.objects.get(shared_secret=shared_secret)
            except OCMShareReceived.DoesNotExist:
                return Response(request.data)

            if ocm_share_received:
                try:
                    ocm_share_received.delete()
                except Exception as e:
                    logger.error(e)
                    return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Invernal Server Error')

        elif notification_type == OCM_NOTIFICATION_SHARE_DECLINED:
            """
            Consumer declined share, then delete ocm_share record on Provider
            """
            try:
                ocm_share = OCMShare.objects.get(shared_secret=shared_secret)
            except OCMShareReceived.DoesNotExist:
                return Response(request.data)

            if ocm_share:
                try:
                    ocm_share.delete()
                except Exception as e:
                    logger.error(e)
                    return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Invernal Server Error')

        return Response(request.data)


class OCMSharesPrepareView(APIView):
    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAuthenticated,)
    throttle_classes = (UserRateThrottle,)

    def get(self, request):
        """
        list ocm shares of request user, filt by repo_id
        """
        repo_id = request.GET.get('repo_id', '')
        if repo_id:
            ocm_shares = OCMShare.objects.filter(repo_id=repo_id, from_user=request.user.username)
        else:
            ocm_shares = OCMShare.objects.filter(from_user=request.user.username)

        ocm_share_list = []
        for ocm_share in ocm_shares:
            ocm_info = ocm_share.to_dict()
            ocm_info['to_server_name'] = get_server_name_by_url(ocm_share.to_server_url)
            ocm_share_list.append(ocm_info)

        return Response({'ocm_share_list': ocm_share_list})

    def post(self, request):
        """
        prepare provider server info for ocm, and send post request to consumer
        three step:
        1. send get request to remote server, ask if support ocm, and get other info
        2. send post request to remote server, remote server create a recored in remote
        ocm_share_received table
        3. store a recored in local ocm_share table
        """

        # argument check
        to_user = request.data.get('to_user', '')
        if not to_user:
            error_msg = 'to_user invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        to_server_url = request.data.get('to_server_url', '').lower().strip()
        if not to_server_url or not is_valid_url(to_server_url):
            error_msg = 'to_server_url %s invalid.' % to_server_url
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        repo_id = request.data.get('repo_id', '')
        if not repo_id:
            error_msg = 'repo_id invalid.'
            return api_error(status.HTTP_400_BAD_REQUEST, error_msg)

        repo = seafile_api.get_repo(repo_id)
        if not repo:
            return api_error(status.HTTP_404_NOT_FOUND, 'Library %s not found.' % repo_id)

        path = request.data.get('path', '/')

        # TODO
        # 1. folder check
        # 2. encrypted repo check
        #
        # if seafile_api.get_dir_id_by_path(repo.id, path) is None:
        #     return api_error(status.HTTP_404_NOT_FOUND, 'Folder %s not found.' % path)
        #
        # if repo.encrypted and path != '/':
        #     return api_error(status.HTTP_400_BAD_REQUEST, 'Folder invalid.')

        permission = request.data.get('permission', PERMISSION_READ)
        if permission not in get_available_repo_perms():
            return api_error(status.HTTP_400_BAD_REQUEST, 'permission invalid.')

        username = request.user.username
        repo_owner = get_repo_owner(request, repo_id)
        if repo_owner != username:
            return api_error(status.HTTP_403_FORBIDDEN, 'Permission denied.')

        if OCMShare.objects.filter(
            from_user=request.user.username,
            to_user=to_user,
            to_server_url=to_server_url,
            repo_id=repo_id,
            repo_name=repo.repo_name,
            path=path,
        ).exists():
            return api_error(status.HTTP_400_BAD_REQUEST, 'same share already exists.')

        consumer_protocol = get_remote_protocol(to_server_url + OCM_PROTOCOL_URL)

        shared_secret = gen_shared_secret()
        from_user = username
        post_data = {
            'shareWith': to_user,
            'name': repo.repo_name,
            'description': '',
            'providerId': OCM_PROVIDER_ID,
            'owner': repo_owner,
            'sender': from_user,
            'ownerDisplayName': email2nickname(repo_owner),
            'senderDisplayName': email2nickname(from_user),
            'shareType': consumer_protocol['resourceTypes']['shareTypes'][0],  # currently only support user type
            'resourceType': consumer_protocol['resourceTypes']['name'],  # currently only support repo
            'protocol': {
                'name': OCM_SEAFILE_PROTOCOL,
                'options': {
                    'sharedSecret': shared_secret,
                    'permissions': SEAFILE_PERMISSION2OCM_PERMISSION[permission],
                    'repoId': repo_id,
                    'seafileServiceURL': check_url_slash(config.SERVICE_URL),
                },
            },
        }
        url = consumer_protocol['endPoint'] + OCM_CREATE_SHARE_URL
        try:
            requests.post(url, json=post_data)
        except Exception as e:
            logging.error(e)
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')

        ocm_share = OCMShare.objects.add(
            shared_secret=shared_secret,
            from_user=request.user.username,
            to_user=to_user,
            to_server_url=to_server_url,
            repo_id=repo_id,
            repo_name=repo.repo_name,
            path=path,
            permission=permission,
        )

        ocm_info = ocm_share.to_dict()
        ocm_info['to_server_name'] = get_server_name_by_url(ocm_share.to_server_url)

        return Response(ocm_info)


class OCMSharePrepareView(APIView):
    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAuthenticated,)
    throttle_classes = (UserRateThrottle,)

    def delete(self, request, pk):
        """
        delete an share received record
        """
        try:
            ocm_share = OCMShare.objects.get(pk=pk)
        except OCMShareReceived.DoesNotExist:
            error_msg = 'OCMShare %s not found.' % pk
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        if ocm_share.from_user != request.user.username:
            error_msg = 'permission denied.'
            return api_error(status.HTTP_403_FORBIDDEN, error_msg)

        to_server_url = ocm_share.to_server_url
        shared_secret = ocm_share.shared_secret

        consumer_protocol = get_remote_protocol(to_server_url + OCM_PROTOCOL_URL)

        # send unshare notification to consumer
        post_data = {
            'notificationType': OCM_NOTIFICATION_SHARE_UNSHARED,
            'resourceType': OCM_RESOURCE_TYPE_LIBRARY,
            'providerId': OCM_PROVIDER_ID,
            'notification': {
                'sharedSecret': shared_secret,
                'message': '',
            },
        }

        url = consumer_protocol['endPoint'] + OCM_NOTIFICATION_URL
        try:
            requests.post(url, json=post_data)
        except Exception as e:
            logging.error(e)
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')

        try:
            ocm_share.delete()
        except Exception as e:
            logger.error(e)
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')

        return Response({'success': True})


class OCMSharesReceivedView(APIView):
    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAuthenticated,)
    throttle_classes = (UserRateThrottle,)

    def get(self, request):
        """
        list ocm shares received
        """
        ocm_share_received_list = []
        ocm_shares_received = OCMShareReceived.objects.filter(to_user=request.user.username)
        for ocm_share_received in ocm_shares_received:
            ocm_share_received_list.append(ocm_share_received.to_dict())
        return Response({'ocm_share_received_list': ocm_share_received_list})


class OCMShareReceivedView(APIView):
    authentication_classes = (TokenAuthentication, SessionAuthentication)
    permission_classes = (IsAuthenticated,)
    throttle_classes = (UserRateThrottle,)

    def delete(self, request, pk):
        """
        delete an share received record
        """
        try:
            ocm_share_received = OCMShareReceived.objects.get(pk=pk)
        except OCMShareReceived.DoesNotExist:
            error_msg = 'OCMShareReceived %s not found.' % pk
            return api_error(status.HTTP_404_NOT_FOUND, error_msg)

        if ocm_share_received.to_user != request.user.username:
            error_msg = 'permission denied.'
            return api_error(status.HTTP_403_FORBIDDEN, error_msg)

        from_server_url = ocm_share_received.from_server_url
        shared_secret = ocm_share_received.shared_secret

        provider_protocol = get_remote_protocol(from_server_url + OCM_PROTOCOL_URL)

        # send unshare notification to consumer
        post_data = {
            'notificationType': OCM_NOTIFICATION_SHARE_DECLINED,
            'resourceType': OCM_RESOURCE_TYPE_LIBRARY,
            'providerId': OCM_PROVIDER_ID,
            'notification': {
                'sharedSecret': shared_secret,
                'message': '',
            },
        }

        url = provider_protocol['endPoint'] + OCM_NOTIFICATION_URL
        try:
            requests.post(url, json=post_data)
        except Exception as e:
            logging.error(e)
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')

        try:
            ocm_share_received.delete()
        except Exception as e:
            logger.error(e)
            return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, 'Internal Server Error')

        return Response({'success': True})
