from __future__ import unicode_literals

from django.core.exceptions import ValidationError as DjangoValidationError
from django.utils.translation import ugettext_lazy as _

from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from rest_framework.reverse import reverse

from mayan.apps.documents.models import DocumentType
from mayan.apps.documents.serializers import DocumentTypeSerializer
from mayan.apps.user_management.serializers import UserSerializer

from .models import (
    Workflow, WorkflowInstance, WorkflowInstanceLogEntry, WorkflowState,
    WorkflowTransition
)


class NewWorkflowDocumentTypeSerializer(serializers.Serializer):
    document_type_pk = serializers.IntegerField(
        help_text=_('Primary key of the document type to be added.')
    )

    def create(self, validated_data):
        document_type = DocumentType.objects.get(
            pk=validated_data['document_type_pk']
        )
        self.context['workflow'].document_types.add(document_type)

        return validated_data


class WorkflowDocumentTypeSerializer(DocumentTypeSerializer):
    workflow_document_type_url = serializers.SerializerMethodField(
        help_text=_(
            'API URL pointing to a document type in relation to the '
            'workflow to which it is attached. This URL is different than '
            'the canonical document type URL.'
        )
    )

    class Meta(DocumentTypeSerializer.Meta):
        fields = DocumentTypeSerializer.Meta.fields + (
            'workflow_document_type_url',
        )
        read_only_fields = DocumentTypeSerializer.Meta.fields

    def get_workflow_document_type_url(self, instance):
        return reverse(
            'rest_api:workflow-document-type-detail', args=(
                self.context['workflow'].pk, instance.pk
            ), request=self.context['request'], format=self.context['format']
        )


class WorkflowStateSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.SerializerMethodField()
    workflow_url = serializers.SerializerMethodField()

    class Meta:
        fields = (
            'completion', 'id', 'initial', 'label', 'url', 'workflow_url',
        )
        model = WorkflowState

    def create(self, validated_data):
        validated_data['workflow'] = self.context['workflow']
        return super(WorkflowStateSerializer, self).create(validated_data)

    def get_url(self, instance):
        return reverse(
            'rest_api:workflowstate-detail', args=(
                instance.workflow.pk, instance.pk
            ), request=self.context['request'], format=self.context['format']
        )

    def get_workflow_url(self, instance):
        return reverse(
            'rest_api:workflow-detail', args=(
                instance.workflow.pk,
            ), request=self.context['request'], format=self.context['format']
        )


class WorkflowTransitionSerializer(serializers.HyperlinkedModelSerializer):
    destination_state = WorkflowStateSerializer()
    origin_state = WorkflowStateSerializer()
    url = serializers.SerializerMethodField()
    workflow_url = serializers.SerializerMethodField()

    class Meta:
        fields = (
            'destination_state', 'id', 'label', 'origin_state', 'url',
            'workflow_url',
        )
        model = WorkflowTransition

    def get_url(self, instance):
        return reverse(
            'rest_api:workflowtransition-detail', args=(
                instance.workflow.pk, instance.pk
            ), request=self.context['request'], format=self.context['format']
        )

    def get_workflow_url(self, instance):
        return reverse(
            'rest_api:workflow-detail', args=(
                instance.workflow.pk,
            ), request=self.context['request'], format=self.context['format']
        )


class WritableWorkflowTransitionSerializer(serializers.ModelSerializer):
    destination_state_pk = serializers.IntegerField(
        help_text=_('Primary key of the destination state to be added.'),
        write_only=True
    )
    origin_state_pk = serializers.IntegerField(
        help_text=_('Primary key of the origin state to be added.'),
        write_only=True
    )
    url = serializers.SerializerMethodField()
    workflow_url = serializers.SerializerMethodField()

    class Meta:
        fields = (
            'destination_state_pk', 'id', 'label', 'origin_state_pk', 'url',
            'workflow_url',
        )
        model = WorkflowTransition

    def create(self, validated_data):
        validated_data['destination_state'] = WorkflowState.objects.get(
            pk=validated_data.pop('destination_state_pk')
        )
        validated_data['origin_state'] = WorkflowState.objects.get(
            pk=validated_data.pop('origin_state_pk')
        )

        validated_data['workflow'] = self.context['workflow']
        return super(WritableWorkflowTransitionSerializer, self).create(
            validated_data
        )

    def get_url(self, instance):
        return reverse(
            'rest_api:workflowtransition-detail', args=(
                instance.workflow.pk, instance.pk
            ), request=self.context['request'], format=self.context['format']
        )

    def get_workflow_url(self, instance):
        return reverse(
            'rest_api:workflow-detail', args=(
                instance.workflow.pk,
            ), request=self.context['request'], format=self.context['format']
        )

    def update(self, instance, validated_data):
        validated_data['destination_state'] = WorkflowState.objects.get(
            pk=validated_data.pop('destination_state_pk')
        )
        validated_data['origin_state'] = WorkflowState.objects.get(
            pk=validated_data.pop('origin_state_pk')
        )

        return super(WritableWorkflowTransitionSerializer, self).update(
            instance, validated_data
        )


class WorkflowSerializer(serializers.HyperlinkedModelSerializer):
    document_types_url = serializers.HyperlinkedIdentityField(
        view_name='rest_api:workflow-document-type-list'
    )
    image_url = serializers.SerializerMethodField()
    states = WorkflowStateSerializer(many=True, required=False)
    transitions = WorkflowTransitionSerializer(many=True, required=False)

    class Meta:
        extra_kwargs = {
            'url': {'view_name': 'rest_api:workflow-detail'},
        }
        fields = (
            'document_types_url', 'image_url', 'id', 'internal_name', 'label',
            'states', 'transitions', 'url'
        )
        model = Workflow

    def get_image_url(self, instance):
        return reverse(
            'rest_api:workflow-image', args=(
                instance.pk,
            ), request=self.context['request'], format=self.context['format']
        )


class WorkflowInstanceLogEntrySerializer(serializers.ModelSerializer):
    document_workflow_url = serializers.SerializerMethodField()
    transition = WorkflowTransitionSerializer(read_only=True)
    user = UserSerializer(read_only=True)

    class Meta:
        fields = (
            'comment', 'datetime', 'document_workflow_url', 'transition',
            'user'
        )
        model = WorkflowInstanceLogEntry

    def get_document_workflow_url(self, instance):
        return reverse(
            'rest_api:workflowinstance-detail', args=(
                instance.workflow_instance.document.pk,
                instance.workflow_instance.pk,
            ), request=self.context['request'], format=self.context['format']
        )


class WorkflowInstanceSerializer(serializers.ModelSerializer):
    current_state = WorkflowStateSerializer(
        read_only=True, source='get_current_state'
    )
    document_workflow_url = serializers.SerializerMethodField(
        help_text=_(
            'API URL pointing to a workflow in relation to the '
            'document to which it is attached. This URL is different than '
            'the canonical workflow URL.'
        )
    )
    last_log_entry = WorkflowInstanceLogEntrySerializer(
        read_only=True, source='get_last_log_entry'
    )
    log_entries_url = serializers.SerializerMethodField(
        help_text=_('A link to the entire history of this workflow.')
    )
    transition_choices = WorkflowTransitionSerializer(
        many=True, read_only=True, source='get_transition_choices'
    )
    workflow = WorkflowSerializer(read_only=True)

    class Meta:
        fields = (
            'current_state', 'document_workflow_url', 'last_log_entry',
            'log_entries_url', 'transition_choices', 'workflow',
        )
        model = WorkflowInstance

    def get_document_workflow_url(self, instance):
        return reverse(
            'rest_api:workflowinstance-detail', args=(
                instance.document.pk, instance.pk,
            ), request=self.context['request'], format=self.context['format']
        )

    def get_log_entries_url(self, instance):
        return reverse(
            'rest_api:workflowinstancelogentry-list', args=(
                instance.document.pk, instance.pk,
            ), request=self.context['request'], format=self.context['format']
        )


class WritableWorkflowSerializer(serializers.ModelSerializer):
    document_types_pk_list = serializers.CharField(
        help_text=_(
            'Comma separated list of document type primary keys to which this '
            'workflow will be attached.'
        ), required=False
    )

    class Meta:
        extra_kwargs = {
            'url': {'view_name': 'rest_api:workflow-detail'},
        }
        fields = (
            'document_types_pk_list', 'label', 'id', 'url',
        )
        model = Workflow

    def _add_document_types(self, document_types_pk_list, instance):
        instance.document_types.add(
            *DocumentType.objects.filter(
                pk__in=document_types_pk_list.split(',')
            )
        )

    def create(self, validated_data):
        document_types_pk_list = validated_data.pop(
            'document_types_pk_list', ''
        )

        instance = super(WritableWorkflowSerializer, self).create(
            validated_data
        )

        if document_types_pk_list:
            self._add_document_types(
                document_types_pk_list=document_types_pk_list,
                instance=instance
            )

        return instance

    def update(self, instance, validated_data):
        document_types_pk_list = validated_data.pop(
            'document_types_pk_list', ''
        )

        instance = super(WritableWorkflowSerializer, self).update(
            instance, validated_data
        )

        if document_types_pk_list:
            instance.documents.clear()
            self._add_documents(
                document_types_pk_list=document_types_pk_list,
                instance=instance
            )

        return instance


class WritableWorkflowInstanceLogEntrySerializer(serializers.ModelSerializer):
    document_workflow_url = serializers.SerializerMethodField()
    transition_pk = serializers.IntegerField(
        help_text=_('Primary key of the transition to be added.'),
        write_only=True
    )
    transition = WorkflowTransitionSerializer(read_only=True)
    user = UserSerializer(read_only=True)

    class Meta:
        fields = (
            'comment', 'datetime', 'document_workflow_url', 'transition',
            'transition_pk', 'user'
        )
        model = WorkflowInstanceLogEntry

    def get_document_workflow_url(self, instance):
        return reverse(
            'rest_api:workflowinstance-detail', args=(
                instance.workflow_instance.document.pk,
                instance.workflow_instance.pk,
            ), request=self.context['request'], format=self.context['format']
        )

    def validate(self, attrs):
        attrs['user'] = self.context['request'].user
        attrs['workflow_instance'] = self.context['workflow_instance']
        attrs['transition'] = WorkflowTransition.objects.get(
            pk=attrs.pop('transition_pk')
        )

        instance = WorkflowInstanceLogEntry(**attrs)

        try:
            instance.full_clean()
        except DjangoValidationError as exception:
            raise ValidationError(exception)

        return attrs
