from ldt.api.ldt.authentication import (SessionAuthentication,
    MultiAuthentication, ApiKeyAuthentication)
from ldt.api.ldt.resources import ContentResource
from ldt.api.ldt.resources.user import UserResource
from ldt.api.ldt.serializers.cinelabserializer import CinelabSerializer
from ldt.ldt_utils.models import Project
from ldt.security import protect_models, unprotect_models
from ldt.security.permissionchecker import check_object_perm_for_user
import logging

from django import VERSION as django_version
from django.conf import settings
from django.conf.urls import url
from django.core.exceptions import ObjectDoesNotExist
from django.db import transaction
from guardian.shortcuts import assign_perm
from tastypie import fields, http
from tastypie.authorization import Authorization
from tastypie.exceptions import BadRequest
from tastypie.resources import Bundle, ModelResource, ALL
from tastypie.utils import dict_strip_unicode_keys


logger = logging.getLogger(__name__)

class ProjectResource(ModelResource):
    contents = fields.ManyToManyField(ContentResource, 'contents')
    owner = fields.ForeignKey(UserResource, 'owner')
    class Meta:
        allowed_methods = ['get', 'post', 'put']
        authorization = Authorization()  # BE CAREFUL WITH THAT, it's unsecure
        authentication = MultiAuthentication(ApiKeyAuthentication(), SessionAuthentication())
        resource_name = 'projects'
        queryset = Project.objects.all()
        serializer = CinelabSerializer()
        filtering = {
            'state' : ALL,
            'ldt_id' : ALL,
            'title' : ALL
        }
        # In the future version :
        # detail_uri_name = 'ldt_id'


    def get_object_list(self, request):
        return Project.safe_objects.all()

    def prepend_urls(self):
        return [
            url(r"^(?P<resource_name>%s)/(?P<ldt_id>[\w\d_.-]+)/$" % self._meta.resource_name, self.wrap_view('dispatch_detail'), name="api_dispatch_detail"),
        ]

    def get_resource_uri(self, bundle_or_obj=None, url_name='api_dispatch_list'):
        if bundle_or_obj is None:
            return super(ProjectResource, self).get_resource_uri(bundle_or_obj, url_name)
        kwargs = {
            'resource_name': self._meta.resource_name,
            'api_name': self._meta.api_name
        }
        if isinstance(bundle_or_obj, Bundle):
            kwargs['ldt_id'] = bundle_or_obj.obj.ldt_id
        else:
            kwargs['ldt_id'] = bundle_or_obj.ldt_id
        return self._build_reverse_url("api_dispatch_detail", kwargs=kwargs)

    # Create a new project. Used with post_detail and with no ldt_id in the url
    # If there is a "source" argument in the data with a valid id, then it will create a copy of the source project
    # Additionally if there is also a "publish" argument in the data, the copied project will immediately be published
    # Note: namespace + prefix for iri cinelab extensions : @prefix iricle:http://ns.iri-research.org/cle/1.0# 
    @transaction.atomic
    def obj_create(self, bundle, **kwargs):
        request = bundle.request

        if request is None:
            raise BadRequest("Request object must be passed as an argument")
        unprotect_models()
        #logger.debug("Bundle : %r", bundle)
        #logger.debug("Bundle data : %r", bundle.data)
        # if source is not none then try copy
        if "source" in request.GET and request.GET["source"]:
            #make sure taht the content-type is application/cinelab
            content_type = request.META.get('CONTENT_TYPE', None)
            if not "application/cinelab" in content_type:
                raise BadRequest("The contenttype MUST be application/cinelab")
            try:
                source_proj = Project.objects.get(ldt_id=request.GET['source'])
            except ObjectDoesNotExist:
                raise BadRequest("Source project could not be found")
            if not check_object_perm_for_user(source_proj, "view_project", request.user):
                raise BadRequest("User has no right to view the project.")
            logger.debug('Data : %r' % bundle.data)
            title = bundle.data.get('title' , "") or source_proj.title
            description = bundle.data.get('description', "") or source_proj.description
            bundle.obj = source_proj.copy_project(user=request.user, title=title, description=description)
            if bundle.data.get('state', -1) >= 0:
                bundle.obj.state = bundle.data.get('state', -1)
            bundle.obj.save()
        else:
            bundle = super(ProjectResource, self).obj_create(bundle, **kwargs)
        # Assign permission for the owner
        assign_perm('view_project', request.user, bundle.obj)
        assign_perm('change_project', request.user, bundle.obj)
        # Since the project is published by default, we assign permission for the everyone group

        if bundle.data.get('state', -1) == Project.PUBLISHED:
            bundle.obj.publish()

        protect_models()
        return bundle


    # Prevent for put_list and delete all objects.
    def obj_delete_list(self, bundle, **kwargs):
        raise BadRequest("PUT with a list of projects is forbidden.")


    def hydrate_image(self, bundle):
        image_data = bundle.data.get('image', None)
        if image_data and image_data.startswith(settings.MEDIA_URL):
            bundle.data['image'] = image_data[len(settings.MEDIA_URL):]
        return bundle

    # Updates an existing project. Used with post_detail and with a ldt_id in the url
    @transaction.atomic
    def post_detail(self, request, **kwargs):
        # Inspired by put_detail but we only update an object. We can not create one.
        """
        Since always_return_data is not set to True, 
        the request returns accepted (202) if the project is well updated
        and Not Found (404) if the id in url or datas were incorrect.
        """
        if django_version >= (1, 4):
            body = request.body
        else:
            body = request.raw_post_data
        deserialized = self.deserialize(request, body, format=request.META.get('CONTENT_TYPE', 'application/json'))
        deserialized = self.alter_deserialized_detail_data(request, deserialized)
        bundle = self.build_bundle(data=dict_strip_unicode_keys(deserialized), request=request)

        try:
            # We have to check if the user can change the project BEFORE calling obj_update
            proj = Project.objects.get(ldt_id=bundle.data['ldt_id'])
            if check_object_perm_for_user(proj, "change_project", request.user):
                # Even if the user has the guardian right to change the project,
                # tastypie's save_m2m will raise an error. So we unprotect just for this saving.
                unprotect_models()
                # Here we hack self.obj_update because it bugs : add request object to data and makes self.obj_get bug.
                # updated_bundle = self.obj_update(bundle, request=request, **self.remove_api_resource_names(kwargs))
                try:
                    bundle.obj = self.obj_get(bundle=bundle, **self.remove_api_resource_names(kwargs))
                except ObjectDoesNotExist:
                    return http.HttpNotFound("A model instance matching the provided arguments could not be found.")
                bundle = self.full_hydrate(bundle)
                updated_bundle = self.save(bundle)
                # Save is done, we can reactivate guardian rights
                protect_models()
            else:
                raise BadRequest("User has no right to change the project.")

            if not self._meta.always_return_data:
                return http.HttpAccepted()
            else:
                updated_bundle = self.full_dehydrate(updated_bundle)
                updated_bundle = self.alter_detail_data_to_serialize(request, updated_bundle)
                return self.create_response(request, updated_bundle, response_class=http.HttpAccepted)
        except Exception as e:
            return http.HttpBadRequest("Datas are not correct.\nError = " + str(e) + "\n")
        return http.HttpResponse("Done")


