from __future__ import unicode_literals

import datetime
import logging

import jwt
from django.core.exceptions import ValidationError, ObjectDoesNotExist
from django.db import transaction
from django.db.models import OuterRef, Subquery, Count, IntegerField
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from rest_framework import exceptions as rf_exceptions
from rest_framework import serializers
from rest_framework.reverse import reverse

from waldur_core.core import serializers as core_serializers
from waldur_core.core import signals as core_signals
from waldur_core.core import utils as core_utils
from waldur_core.core.fields import NaturalChoiceField
from waldur_core.core.serializers import GenericRelatedField
from waldur_core.media.serializers import ProtectedMediaSerializerMixin, ProtectedFileField
from waldur_core.quotas.serializers import BasicQuotaSerializer
from waldur_core.structure import models as structure_models, SupportedServices
from waldur_core.structure import permissions as structure_permissions
from waldur_core.structure import serializers as structure_serializers
from waldur_core.structure.managers import filter_queryset_for_user
from waldur_core.structure.tasks import connect_shared_settings
from waldur_mastermind.common.serializers import validate_options
from waldur_mastermind.common import exceptions
from waldur_mastermind.marketplace.permissions import check_availability_of_auto_approving
from waldur_mastermind.marketplace.plugins import manager
from waldur_mastermind.marketplace.utils import validate_order_item, validate_limits, create_offering_components
from waldur_mastermind.support import serializers as support_serializers
from waldur_mastermind.marketplace.processors import CreateResourceProcessor

from . import models, attribute_types, plugins, permissions, tasks

logger = logging.getLogger(__name__)


class ServiceProviderSerializer(core_serializers.AugmentedSerializerMixin,
                                serializers.HyperlinkedModelSerializer):
    class Meta(object):
        model = models.ServiceProvider
        fields = (
            'url', 'uuid', 'created', 'description', 'enable_notifications',
            'customer', 'customer_name', 'customer_uuid', 'customer_image',
            'customer_abbreviation', 'customer_native_name',
        )
        related_paths = {
            'customer': ('uuid', 'name', 'native_name', 'abbreviation')
        }
        protected_fields = ('customer',)
        extra_kwargs = {
            'url': {'lookup_field': 'uuid', 'view_name': 'marketplace-service-provider-detail'},
            'customer': {'lookup_field': 'uuid'},
        }

    customer_image = ProtectedFileField(source='customer.image', read_only=True)

    def validate(self, attrs):
        if not self.instance:
            permissions.can_register_service_provider(self.context['request'], attrs['customer'])
        return attrs


class NestedAttributeOptionSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = models.AttributeOption
        fields = ('key', 'title')


class NestedAttributeSerializer(serializers.ModelSerializer):
    options = NestedAttributeOptionSerializer(many=True)

    class Meta(object):
        model = models.Attribute
        fields = ('key', 'title', 'type', 'options', 'required', 'default')


class NestedSectionSerializer(serializers.ModelSerializer):
    attributes = NestedAttributeSerializer(many=True, read_only=True)

    class Meta(object):
        model = models.Section
        fields = ('key', 'title', 'attributes', 'is_standalone')


class NestedColumnSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = models.CategoryColumn
        fields = ('index', 'title', 'attribute', 'widget')


class CategoryComponentSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = models.CategoryComponent
        fields = ('type', 'name', 'description', 'measured_unit')


class CategorySerializer(ProtectedMediaSerializerMixin,
                         core_serializers.AugmentedSerializerMixin,
                         core_serializers.RestrictedSerializerMixin,
                         serializers.HyperlinkedModelSerializer):
    offering_count = serializers.ReadOnlyField()
    sections = NestedSectionSerializer(many=True, read_only=True)
    columns = NestedColumnSerializer(many=True, read_only=True)
    components = CategoryComponentSerializer(many=True, read_only=True)

    @staticmethod
    def eager_load(queryset, request):
        offerings = models.Offering.objects \
            .filter(state=models.Offering.States.ACTIVE) \
            .filter(category=OuterRef('pk')) \
            .filter_for_user(request.user)

        allowed_customer_uuid = request.query_params.get('allowed_customer_uuid')
        if allowed_customer_uuid and core_utils.is_uuid_like(allowed_customer_uuid):
            offerings = offerings.filter_for_customer(allowed_customer_uuid)

        project_uuid = request.query_params.get('project_uuid')
        if project_uuid and core_utils.is_uuid_like(project_uuid):
            offerings = offerings.filter_for_project(project_uuid)

        offerings = offerings \
            .annotate(count=Count('*'))\
            .values('count')

        # Workaround for Django bug:
        # https://code.djangoproject.com/ticket/28296
        # It allows to remove extra GROUP BY clause from the subquery.
        offerings.query.group_by = []

        offering_count = Subquery(offerings[:1], output_field=IntegerField())
        queryset = queryset.annotate(offering_count=offering_count)
        return queryset.prefetch_related('sections', 'sections__attributes')

    class Meta(object):
        model = models.Category
        fields = ('url', 'uuid', 'title', 'description', 'icon', 'offering_count',
                  'sections', 'columns', 'components')
        extra_kwargs = {
            'url': {'lookup_field': 'uuid', 'view_name': 'marketplace-category-detail'},
        }


PriceSerializer = serializers.DecimalField(
    min_value=0,
    max_digits=models.PlanComponent.PRICE_MAX_DIGITS,
    decimal_places=models.PlanComponent.PRICE_DECIMAL_PLACES,
)


class BasePlanSerializer(core_serializers.AugmentedSerializerMixin,
                         serializers.HyperlinkedModelSerializer):
    prices = serializers.DictField(child=PriceSerializer, write_only=True, required=False)
    quotas = serializers.DictField(child=serializers.IntegerField(min_value=0),
                                   write_only=True, required=False)

    class Meta(object):
        model = models.Plan
        fields = ('url', 'uuid', 'name', 'description',
                  'article_code', 'product_code',
                  'prices', 'quotas', 'max_amount', 'archived', 'is_active',
                  'unit_price', 'unit', 'init_price', 'switch_price')
        read_ony_fields = ('unit_price', 'archived')
        extra_kwargs = {
            'url': {'lookup_field': 'uuid', 'view_name': 'marketplace-plan-detail'},
        }

    def get_fields(self):
        fields = super(BasePlanSerializer, self).get_fields()
        method = self.context['view'].request.method
        if method == 'GET':
            fields['prices'] = serializers.SerializerMethodField()
            fields['quotas'] = serializers.SerializerMethodField()
        return fields

    def get_prices(self, plan):
        return {item.component.type: item.price for item in plan.components.all()}

    def get_quotas(self, plan):
        return {item.component.type: item.amount for item in plan.components.all()}


class PlanDetailsSerializer(BasePlanSerializer):
    class Meta(BasePlanSerializer.Meta):
        model = models.Plan
        fields = BasePlanSerializer.Meta.fields + ('offering',)
        protected_fields = ('offering',)
        extra_kwargs = {
            'url': {'lookup_field': 'uuid', 'view_name': 'marketplace-plan-detail'},
            'offering': {'lookup_field': 'uuid', 'view_name': 'marketplace-offering-detail'},
        }

    def validate(self, attrs):
        if not self.instance:
            structure_permissions.is_owner(self.context['request'], None, attrs['offering'].customer)

        return attrs


class PlanUsageRequestSerializer(serializers.Serializer):
    offering_uuid = serializers.UUIDField(required=False)
    customer_provider_uuid = serializers.UUIDField(required=False)
    o = serializers.ChoiceField(choices=(
        'usage', 'limit', 'remaining',
        '-usage', '-limit', '-remaining',
    ), required=False)


class PlanUsageResponseSerializer(serializers.Serializer):
    plan_uuid = serializers.ReadOnlyField(source='uuid')
    plan_name = serializers.ReadOnlyField(source='name')

    limit = serializers.ReadOnlyField()
    usage = serializers.ReadOnlyField()
    remaining = serializers.ReadOnlyField()

    offering_uuid = serializers.ReadOnlyField(source='offering.uuid')
    offering_name = serializers.ReadOnlyField(source='offering.name')

    customer_provider_uuid = serializers.ReadOnlyField(source='offering.customer.uuid')
    customer_provider_name = serializers.ReadOnlyField(source='offering.customer.name')


class NestedScreenshotSerializer(ProtectedMediaSerializerMixin, serializers.ModelSerializer):
    class Meta(object):
        model = models.Screenshot
        fields = ('name', 'description', 'image', 'thumbnail')


class NestedOfferingFileSerializer(ProtectedMediaSerializerMixin, serializers.ModelSerializer):
    class Meta(object):
        model = models.OfferingFile
        fields = ('name', 'created', 'file',)


class ScreenshotSerializer(ProtectedMediaSerializerMixin,
                           core_serializers.AugmentedSerializerMixin,
                           serializers.HyperlinkedModelSerializer):

    class Meta(object):
        model = models.Screenshot
        fields = ('url', 'uuid', 'name', 'description', 'image', 'thumbnail', 'offering')
        protected_fields = ('offering', 'image')
        extra_kwargs = {
            'url': {'lookup_field': 'uuid'},
            'offering': {'lookup_field': 'uuid', 'view_name': 'marketplace-offering-detail'},
        }

    def validate(self, attrs):
        if not self.instance:
            structure_permissions.is_owner(self.context['request'], None, attrs['offering'].customer)
        return attrs


FIELD_TYPES = (
    'boolean',
    'integer',
    'money',
    'string',
    'text',
    'html_text',
    'select_string',
    'select_openstack_tenant',
    'select_openstack_instance',
    'select_multiple_openstack_instances',
    'date',
    'time',
)


class DefaultField(serializers.Field):
    def to_internal_value(self, data):
        return data


class OptionFieldSerializer(serializers.Serializer):
    type = serializers.ChoiceField(choices=FIELD_TYPES)
    label = serializers.CharField()
    help_text = serializers.CharField(required=False)
    required = serializers.BooleanField(default=False)
    choices = serializers.ListField(child=serializers.CharField(), required=False)
    default = DefaultField(required=False)
    min = serializers.IntegerField(required=False)
    max = serializers.IntegerField(required=False)


class OfferingOptionsSerializer(serializers.Serializer):
    order = serializers.ListField(child=serializers.CharField())
    options = serializers.DictField(child=OptionFieldSerializer())


class OfferingComponentSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = models.OfferingComponent
        fields = ('billing_type', 'type', 'name', 'description', 'measured_unit',
                  'limit_period', 'limit_amount', 'disable_quotas', 'product_code', 'article_code',
                  'max_value', 'min_value')
        extra_kwargs = {
            'billing_type': {'required': True},
        }


class OfferingDetailsSerializer(ProtectedMediaSerializerMixin,
                                core_serializers.AugmentedSerializerMixin,
                                core_serializers.RestrictedSerializerMixin,
                                serializers.HyperlinkedModelSerializer):

    attributes = serializers.JSONField(required=False)
    options = serializers.JSONField(required=False)
    components = OfferingComponentSerializer(required=False, many=True)
    geolocations = core_serializers.GeoLocationField(required=False)
    order_item_count = serializers.SerializerMethodField()
    plans = BasePlanSerializer(many=True, required=False)
    screenshots = NestedScreenshotSerializer(many=True, read_only=True)
    state = serializers.ReadOnlyField(source='get_state_display')
    scope = GenericRelatedField(read_only=True)
    scope_uuid = serializers.ReadOnlyField(source='scope.uuid')
    files = NestedOfferingFileSerializer(many=True, read_only=True)
    quotas = serializers.SerializerMethodField()

    class Meta(object):
        model = models.Offering
        fields = ('url', 'uuid', 'created', 'name', 'description', 'full_description', 'terms_of_service',
                  'customer', 'customer_uuid', 'customer_name',
                  'category', 'category_uuid', 'category_title',
                  'rating', 'attributes', 'options', 'components', 'geolocations',
                  'state', 'native_name', 'native_description', 'vendor_details',
                  'thumbnail', 'order_item_count', 'plans', 'screenshots', 'type', 'shared', 'billable',
                  'scope', 'scope_uuid', 'files', 'quotas', 'paused_reason')
        related_paths = {
            'customer': ('uuid', 'name'),
            'category': ('uuid', 'title'),
        }
        protected_fields = ('customer', 'type')
        read_only_fields = ('state', 'paused_reason')
        extra_kwargs = {
            'url': {'lookup_field': 'uuid', 'view_name': 'marketplace-offering-detail'},
            'customer': {'lookup_field': 'uuid', 'view_name': 'customer-detail'},
            'category': {'lookup_field': 'uuid', 'view_name': 'marketplace-category-detail'},
        }

    def get_order_item_count(self, offering):
        try:
            return offering.quotas.get(name='order_item_count').usage
        except ObjectDoesNotExist:
            return 0

    def get_quotas(self, offering):
        if offering.scope and hasattr(offering.scope, 'quotas'):
            return BasicQuotaSerializer(offering.scope.quotas, many=True, context=self.context).data


class OfferingComponentLimitSerializer(serializers.Serializer):
    min = serializers.IntegerField(min_value=0)
    max = serializers.IntegerField(min_value=0)


class OfferingModifySerializer(OfferingDetailsSerializer):

    class Meta(OfferingDetailsSerializer.Meta):
        model = models.Offering
        fields = OfferingDetailsSerializer.Meta.fields + ('limits',)

    limits = serializers.DictField(child=OfferingComponentLimitSerializer(),
                                   write_only=True, required=False)

    def validate(self, attrs):
        if not self.instance:
            structure_permissions.is_owner(self.context['request'], None, attrs['customer'])

        self._validate_attributes(attrs)
        self._validate_plans(attrs)

        return attrs

    def validate_type(self, offering_type):
        if offering_type not in plugins.manager.backends.keys():
            raise rf_exceptions.ValidationError(_('Invalid value.'))
        return offering_type

    def validate_terms_of_service(self, value):
        if value:
            value = value.strip()
        return value

    def _validate_attributes(self, attrs):
        category = attrs.get('category')
        if category is None and self.instance:
            category = self.instance.category

        attributes = attrs.get('attributes')
        if attributes is not None and not isinstance(attributes, dict):
            raise rf_exceptions.ValidationError({
                'attributes': _('Dictionary is expected.'),
            })

        if attributes is None and self.instance:
            attributes = self.instance.attributes

        if attributes is None:
            attributes = dict()

        category_attributes = models.Attribute.objects.filter(section__category=category)
        required_attributes = category_attributes.filter(required=True).values_list('key', flat=True)
        missing_attributes = set(required_attributes) - set(attributes.keys())

        if missing_attributes:
            raise rf_exceptions.ValidationError({
                'attributes': _('These attributes are required: %s' % ', '.join(sorted(missing_attributes)))
            })

        for attribute in category_attributes:
            value = attributes.get(attribute.key)
            if value is None:
                # Use default attribute value if it is defined
                if attribute.default is not None:
                    attributes[attribute.key] = attribute.default
                continue

            validator = attribute_types.get_attribute_type(attribute.type)
            if not validator:
                continue

            try:
                validator.validate(value, list(attribute.options.values_list('key', flat=True)))
            except ValidationError as e:
                raise rf_exceptions.ValidationError({attribute.key: e.message})

    def validate_options(self, options):
        serializer = OfferingOptionsSerializer(data=options)
        serializer.is_valid(raise_exception=True)
        return serializer.validated_data

    def _validate_plans(self, attrs):
        custom_components = attrs.get('components')
        if not custom_components and self.instance:
            custom_components = self.instance.components.all().values()

        offering_type = attrs.get('type', getattr(self.instance, 'type', None))
        builtin_components = plugins.manager.get_components(offering_type)

        valid_types = set()
        fixed_types = set()

        if builtin_components and attrs.get('components'):
            raise serializers.ValidationError({
                'components': _('Extra components are not allowed.')
            })

        elif builtin_components:
            valid_types = {component.type for component in builtin_components}
            fixed_types = {component.type
                           for component in plugins.manager.get_components(offering_type)
                           if component.billing_type == models.OfferingComponent.BillingTypes.FIXED}

        elif custom_components:
            valid_types = {component['type'] for component in custom_components}
            fixed_types = {component['type'] for component in custom_components
                           if component['billing_type'] == models.OfferingComponent.BillingTypes.FIXED}

        for plan in attrs.get('plans', []):
            plan_name = plan.get('name')

            prices = plan.get('prices', {})
            invalid_components = ', '.join(sorted(set(prices.keys()) - valid_types))
            if invalid_components:
                raise serializers.ValidationError({
                    'plans': _('Invalid price components %s in plan "%s".') % (invalid_components, plan_name)
                })

            quotas = plan.get('quotas', {})
            invalid_components = ', '.join(sorted(set(quotas.keys()) - fixed_types))
            if invalid_components:
                raise serializers.ValidationError({
                    'plans': _('Invalid quota components %s in plan "%s".') % (invalid_components, plan_name),
                })

            plan['unit_price'] = sum(
                prices.get(component, 0) * quotas.get(component, 0)
                for component in fixed_types
            )

    def _create_plan(self, offering, plan_data, components):
        quotas = plan_data.pop('quotas', {})
        prices = plan_data.pop('prices', {})
        plan = models.Plan.objects.create(offering=offering, **plan_data)

        for name, component in components.items():
            models.PlanComponent.objects.create(
                plan=plan,
                component=component,
                amount=quotas.get(name) or 0,
                price=prices.get(name) or 0,
            )

    def _create_plans(self, offering, plans):
        components = {component.type: component for component in offering.components.all()}
        for plan_data in plans:
            self._create_plan(offering, plan_data, components)

    def _update_limits(self, offering, limits):
        for key, values in limits.items():
            models.OfferingComponent.objects.filter(
                offering=offering,
                type=key
            ).update(
                min_value=values['min'],
                max_value=values['max']
            )


class OfferingCreateSerializer(OfferingModifySerializer):
    class Meta(OfferingModifySerializer.Meta):
        fields = OfferingModifySerializer.Meta.fields + ('service_attributes',)

    service_attributes = serializers.JSONField(required=False, write_only=True)

    def validate_plans(self, plans):
        if len(plans) < 1:
            raise serializers.ValidationError({
                'plans': _('At least one plan should be specified.')
            })
        return plans

    @transaction.atomic
    def create(self, validated_data):
        limits = validated_data.pop('limits', {})
        plans = validated_data.pop('plans', [])
        custom_components = validated_data.pop('components', [])

        offering_type = validated_data.get('type')
        service_type = plugins.manager.get_service_type(offering_type)
        if service_type:
            validated_data = self._create_service(service_type, validated_data)

        offering = super(OfferingCreateSerializer, self).create(validated_data)
        create_offering_components(offering, custom_components)
        if limits:
            self._update_limits(offering, limits)
        self._create_plans(offering, plans)

        return offering

    def _create_service(self, service_type, validated_data):
        """
        Marketplace offering model does not accept service_attributes field as is,
        therefore we should remove it from validated_data and create service settings object.
        Then we need to specify created object and offering's scope.
        """
        name = validated_data['name']
        service_attributes = validated_data.pop('service_attributes', {})
        if not service_attributes:
            raise ValidationError({
                'service_attributes': _('This field is required.')
            })
        payload = dict(
            name=name,
            # It is expected that customer URL is passed to the service settings serializer
            customer=self.initial_data['customer'],
            type=service_type,
            **service_attributes
        )
        serializer_class = SupportedServices.get_service_serializer_for_key(service_type)
        serializer = serializer_class(data=payload, context=self.context)
        serializer.is_valid(raise_exception=True)
        service = serializer.save()
        # Usually we don't allow users to create new shared service settings via REST API.
        # That's shared flag is marked as read-only in service settings serializer.
        # But shared offering should be created with shared service settings.
        # That's why we set it to shared only after service settings object is created.
        if validated_data.get('shared'):
            service.settings.shared = True
            service.settings.save()
            # Usually connect shared settings task is called when service is created.
            # But as we set shared flag after serializer has been executed,
            # we need to connect shared settings manually.
            connect_shared_settings(service.settings)
        validated_data['scope'] = service.settings
        return validated_data


class OfferingPauseSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = models.Offering
        fields = ['paused_reason']


class PlanUpdateSerializer(BasePlanSerializer):

    class Meta(BasePlanSerializer.Meta):
        extra_kwargs = {
            'uuid': {'read_only': False},
        }


class OfferingUpdateSerializer(OfferingModifySerializer):

    plans = PlanUpdateSerializer(many=True, required=False, write_only=True)

    def _update_components(self, instance, components):
        resources_exist = models.Resource.objects.filter(offering=instance).exists()

        old_components = {
            component.type: component
            for component in instance.components.all()
        }

        new_components = {
            component['type']: models.OfferingComponent(offering=instance, **component)
            for component in components
        }

        removed_components = set(old_components.keys()) - set(new_components.keys())
        added_components = set(new_components.keys()) - set(old_components.keys())
        updated_components = set(new_components.keys()) & set(old_components.keys())

        builtin_components = plugins.manager.get_components(self.instance.type)
        valid_types = {component.type for component in builtin_components}

        if removed_components & valid_types:
            raise serializers.ValidationError({
                'components': _('These components cannot be removed because they are builtin: %s') %
                ', '.join(removed_components & valid_types)
            })

        if updated_components & valid_types:
            raise serializers.ValidationError({
                'components': _('These components cannot be updated because they are builtin: %s') %
                ', '.join(updated_components & valid_types)
            })

        if removed_components:
            if resources_exist:
                raise serializers.ValidationError({
                    'components': _('These components cannot be removed because they are already used: %s') %
                    ', '.join(removed_components)
                })
            else:
                models.OfferingComponent.objects.filter(type__in=removed_components).delete()

        for key in added_components:
            new_components[key].save()

        COMPONENT_KEYS = (
            'name', 'description',
            'billing_type', 'measured_unit',
            'limit_period', 'limit_amount', 'disable_quotas',
            'product_code', 'article_code',
        )

        for component_key in updated_components:
            new_component = new_components[component_key]
            old_component = old_components[component_key]
            for key in COMPONENT_KEYS:
                setattr(old_component, key, getattr(new_component, key))
            old_component.save()

    def _update_plan_components(self, old_plan, new_plan):
        new_quotas = new_plan.get('quotas', {})
        new_prices = new_plan.get('prices', {})

        new_keys = set(new_quotas.keys()) | set(new_prices.keys())
        old_keys = set(old_plan.components.values_list('component__type', flat=True))

        for key in new_keys - old_keys:
            component = old_plan.offering.components.get(type=key)
            models.PlanComponent.objects.create(plan=old_plan, component=component)

    def _update_quotas(self, old_plan, new_plan):
        new_quotas = new_plan.get('quotas', {})
        new_prices = new_plan.get('prices', {})
        component_map = {
            component.component.type: component
            for component in old_plan.components.all()
        }
        for key, old_component in component_map.items():
            new_amount = new_quotas.get(key, 0)
            if old_component.amount != new_amount:
                old_component.amount = new_amount
                old_component.save(update_fields=['amount'])

            new_price = new_prices.get(key, 0)
            if old_component.price != new_price:
                old_component.price = new_price
                old_component.save(update_fields=['price'])

    def _update_plan_details(self, old_plan, new_plan):
        PLAN_FIELDS = (
            'name', 'description',
            'unit', 'max_amount',
            'product_code', 'article_code',
        )

        for key in PLAN_FIELDS:
            if key in new_plan:
                setattr(old_plan, key, new_plan.get(key))
        old_plan.save()

    def _update_plans(self, offering, new_plans):
        old_plans = offering.plans.all()
        old_ids = set(old_plans.values_list('uuid', flat=True))

        new_map = {plan['uuid']: plan for plan in new_plans if 'uuid' in plan}
        added_plans = [plan for plan in new_plans if 'uuid' not in plan]

        removed_ids = set(old_ids) - set(new_map.keys())
        updated_ids = set(new_map.keys()) & set(old_ids)

        removed_plans = models.Plan.objects.filter(uuid__in=removed_ids).exclude(archived=True)
        updated_plans = {plan.uuid: plan for plan in models.Plan.objects.filter(uuid__in=updated_ids)}

        for plan_uuid, old_plan in updated_plans.items():
            new_plan = new_map[plan_uuid]
            self._update_plan_details(old_plan, new_plan)
            self._update_plan_components(old_plan, new_plan)
            self._update_quotas(old_plan, new_plan)

        if added_plans:
            self._create_plans(offering, added_plans)

        for plan in removed_plans:
            plan.archived = True
            plan.save()

    @transaction.atomic
    def update(self, instance, validated_data):
        """
        Components and plans are specified using nested list serializers with many=True.
        These serializers return empty list even if value is not provided explicitly.
        See also: https://github.com/encode/django-rest-framework/issues/3434
        Consider the case when offering's thumbnail is uploaded, but plans and components are not specified.
        It leads to tricky bug when all components are removed and plans are marked as archived.
        In order to distinguish between case when user asks to remove all plans and
        case when user wants to update only one attribute these we need to check not only
        validated data, but also initial data.
        """
        if 'components' in validated_data:
            components = validated_data.pop('components', [])
            if 'components' in self.initial_data:
                self._update_components(instance, components)
        if 'plans' in validated_data:
            new_plans = validated_data.pop('plans', [])
            if 'plans' in self.initial_data:
                self._update_plans(instance, new_plans)
        limits = validated_data.pop('limits', {})
        if limits:
            self._update_limits(instance, limits)
        offering = super(OfferingUpdateSerializer, self).update(instance, validated_data)
        return offering


class ComponentQuotaSerializer(serializers.ModelSerializer):
    type = serializers.ReadOnlyField(source='component.type')

    class Meta(object):
        model = models.ComponentQuota
        fields = ('type', 'limit', 'usage')


class BaseItemSerializer(core_serializers.AugmentedSerializerMixin,
                         serializers.HyperlinkedModelSerializer,
                         core_serializers.RestrictedSerializerMixin):

    class Meta(object):
        fields = ('offering', 'offering_name', 'offering_uuid',
                  'offering_description', 'offering_thumbnail', 'offering_type',
                  'offering_terms_of_service', 'offering_shared', 'offering_billable',
                  'provider_name', 'provider_uuid',
                  'category_title', 'category_uuid',
                  'plan', 'plan_unit', 'plan_name', 'plan_uuid', 'plan_description',
                  'attributes', 'limits', 'uuid', 'created')
        related_paths = {
            'offering': ('name', 'uuid', 'description', 'type', 'terms_of_service', 'shared', 'billable'),
            'plan': ('unit', 'uuid', 'name', 'description')
        }
        protected_fields = ('offering',)
        extra_kwargs = {
            'offering': {'lookup_field': 'uuid', 'view_name': 'marketplace-offering-detail'},
            'plan': {'lookup_field': 'uuid', 'view_name': 'marketplace-plan-detail'},
        }

    provider_name = serializers.ReadOnlyField(source='offering.customer.name')
    provider_uuid = serializers.ReadOnlyField(source='offering.customer.uuid')
    category_title = serializers.ReadOnlyField(source='offering.category.title')
    category_uuid = serializers.ReadOnlyField(source='offering.category.uuid')
    offering_thumbnail = ProtectedFileField(source='offering.thumbnail', read_only=True)

    def validate_offering(self, offering):
        if not offering.state == models.Offering.States.ACTIVE:
            raise rf_exceptions.ValidationError(_('Offering is not available.'))
        return offering

    def validate(self, attrs):
        offering = attrs.get('offering')
        plan = attrs.get('plan')

        if not offering:
            if not self.instance:
                raise rf_exceptions.ValidationError({
                    'offering': _('This field is required.')
                })
            offering = self.instance.offering

        if plan:
            if plan.offering != offering:
                raise rf_exceptions.ValidationError({
                    'plan': _('This plan is not available for selected offering.')
                })

            validate_plan(plan)

        if offering.options:
            validate_options(offering.options['options'], attrs.get('attributes'))

        limits = attrs.get('limits')
        if limits:
            validate_limits(limits, offering)
        return attrs


class BaseRequestSerializer(BaseItemSerializer):
    type = NaturalChoiceField(
        choices=models.RequestTypeMixin.Types.CHOICES,
        required=False,
        default=models.RequestTypeMixin.Types.CREATE,
    )

    class Meta(BaseItemSerializer.Meta):
        fields = BaseItemSerializer.Meta.fields + ('type',)


class NestedOrderItemSerializer(BaseRequestSerializer):
    class Meta(BaseRequestSerializer.Meta):
        model = models.OrderItem
        fields = BaseRequestSerializer.Meta.fields + (
            'resource_uuid', 'resource_type', 'resource_name',
            'cost', 'state', 'marketplace_resource_uuid', 'error_message',
            'accepting_terms_of_service',
        )

        read_only_fields = ('cost', 'state', 'error_message')
        protected_fields = ('offering', 'plan')

    marketplace_resource_uuid = serializers.ReadOnlyField(source='resource.uuid')
    resource_name = serializers.ReadOnlyField(source='resource.name')
    resource_uuid = serializers.ReadOnlyField(source='resource.backend_uuid')
    resource_type = serializers.ReadOnlyField(source='resource.backend_type')
    state = serializers.ReadOnlyField(source='get_state_display')
    limits = serializers.DictField(child=serializers.IntegerField(), required=False)
    accepting_terms_of_service = serializers.BooleanField(required=False, write_only=True)

    def get_fields(self):
        fields = super(BaseItemSerializer, self).get_fields()
        method = self.context['view'].request.method
        if method == 'GET':
            fields['attributes'] = serializers.ReadOnlyField(source='safe_attributes')
        return fields


class OrderItemDetailsSerializer(NestedOrderItemSerializer):
    class Meta(NestedOrderItemSerializer.Meta):
        fields = NestedOrderItemSerializer.Meta.fields + (
            'order_uuid', 'order_approved_at', 'order_approved_by',
            'created_by_full_name', 'created_by_civil_number',
            'customer_name', 'customer_uuid',
            'project_name', 'project_uuid',
            'old_plan_name', 'new_plan_name',
            'old_plan_uuid', 'new_plan_uuid',
            'old_cost_estimate', 'new_cost_estimate',
            'can_terminate',
        )

    order_uuid = serializers.ReadOnlyField(source='order.uuid')
    order_approved_at = serializers.ReadOnlyField(source='order.approved_at')
    order_approved_by = serializers.ReadOnlyField(source='order.approved_by.full_name')

    created_by_full_name = serializers.ReadOnlyField(source='order.created_by.full_name')
    created_by_civil_number = serializers.ReadOnlyField(source='order.created_by.civil_number')

    customer_name = serializers.ReadOnlyField(source='order.project.customer.name')
    customer_uuid = serializers.ReadOnlyField(source='order.project.customer.uuid')

    project_name = serializers.ReadOnlyField(source='order.project.name')
    project_uuid = serializers.ReadOnlyField(source='order.project.uuid')

    old_plan_name = serializers.ReadOnlyField(source='old_plan.name')
    new_plan_name = serializers.ReadOnlyField(source='plan.name')

    old_plan_uuid = serializers.ReadOnlyField(source='old_plan.uuid')
    new_plan_uuid = serializers.ReadOnlyField(source='plan.uuid')

    old_cost_estimate = serializers.ReadOnlyField(source='resource.cost')
    new_cost_estimate = serializers.ReadOnlyField(source='cost')

    can_terminate = serializers.SerializerMethodField()

    def get_can_terminate(self, order_item):
        if not plugins.manager.can_terminate_order_item(order_item.offering.type):
            return False

        if order_item.state not in (models.OrderItem.States.PENDING, models.OrderItem.States.EXECUTING):
            return False

        return True


class CartItemSerializer(BaseRequestSerializer):
    limits = serializers.DictField(child=serializers.IntegerField(), required=False)
    estimate = serializers.ReadOnlyField(source='cost')
    project = serializers.HyperlinkedRelatedField(
        lookup_field='uuid',
        view_name='project-detail',
        queryset=structure_models.Project.objects.all(),
    )
    project_uuid = serializers.ReadOnlyField(source='project.uuid')
    project_name = serializers.ReadOnlyField(source='project.name')

    class Meta(BaseRequestSerializer.Meta):
        model = models.CartItem
        fields = BaseRequestSerializer.Meta.fields + (
            'estimate', 'project', 'project_name', 'project_uuid'
        )
        protected_fields = BaseRequestSerializer.Meta.protected_fields + ('project',)

    def get_fields(self):
        fields = super(CartItemSerializer, self).get_fields()
        if 'project' in fields:
            fields['project'].queryset = filter_queryset_for_user(
                fields['project'].queryset, self.context['request'].user)
        return fields

    def quotas_validate(self, item, project):
        try:
            with transaction.atomic():
                processor_class = manager.get_processor(item.offering.type, 'create_resource_processor')
                order_params = dict(project=project, created_by=self.context['request'].user)
                order = models.Order(**order_params)
                item_params = get_item_params(item)
                order_item = models.OrderItem(order=order, **item_params)

                if issubclass(processor_class, CreateResourceProcessor):
                    processor = processor_class(order_item)
                    post_data = processor.get_post_data()
                    serializer = processor.get_serializer_class()(data=post_data, context=self.context)
                    serializer.is_valid(raise_exception=True)
                    serializer.save()
                    raise exceptions.TransactionRollback()
        except exceptions.TransactionRollback:
            pass

    @transaction.atomic
    def create(self, validated_data):
        validated_data['user'] = self.context['request'].user
        item = super(CartItemSerializer, self).create(validated_data)
        item.init_cost()
        item.save(update_fields=['cost'])
        self.quotas_validate(item, validated_data['project'])
        return item

    @transaction.atomic
    def update(self, instance, validated_data):
        instance = super(CartItemSerializer, self).update(instance, validated_data)
        instance.init_cost()
        instance.save(update_fields=['cost'])
        return instance


class CartSubmitSerializer(serializers.Serializer):
    project = serializers.HyperlinkedRelatedField(
        queryset=structure_models.Project.objects.all(),
        view_name='project-detail',
        lookup_field='uuid',
        required=True,
    )

    def get_fields(self):
        fields = super(CartSubmitSerializer, self).get_fields()
        project_field = fields['project']
        project_field.queryset = filter_queryset_for_user(
            project_field.queryset, self.context['request'].user)
        return fields

    @transaction.atomic()
    def create(self, validated_data):
        user = self.context['request'].user
        project = validated_data['project']

        items = models.CartItem.objects.filter(user=user, project=project)
        if items.count() == 0:
            raise serializers.ValidationError(_('Shopping cart is empty'))

        order = create_order(project, user, items, self.context['request'])
        items.delete()
        return order


def get_item_params(item):
    return dict(
        offering=item.offering,
        attributes=item.attributes,
        resource=getattr(item, 'resource', None),  # cart item does not have resource
        plan=item.plan,
        old_plan=getattr(item, 'old_plan', None),  # cart item does not have old plan
        limits=item.limits,
        type=item.type,
    )


def create_order(project, user, items, request):
    order_params = dict(project=project, created_by=user)
    order = models.Order.objects.create(**order_params)

    for item in items:
        if item.type in (models.OrderItem.Types.UPDATE, models.OrderItem.Types.TERMINATE) and \
                item.resource and models.OrderItem.objects.filter(
            resource=item.resource,
            state__in=(models.OrderItem.States.PENDING, models.OrderItem.States.EXECUTING)
        ).exists():
            raise rf_exceptions.ValidationError(_('Pending order item for resource already exists.'))

        try:
            params = get_item_params(item)
            order_item = order.add_item(**params)
        except ValidationError as e:
            raise rf_exceptions.ValidationError(e)
        validate_order_item(order_item, request)

    order.init_total_cost()
    order.save()

    if check_availability_of_auto_approving(items, user, project):
        tasks.approve_order(order, user)
    else:
        transaction.on_commit(lambda: tasks.notify_order_approvers.delay(order.uuid))

    return order


class OrderSerializer(structure_serializers.PermissionFieldFilteringMixin,
                      core_serializers.AugmentedSerializerMixin,
                      serializers.HyperlinkedModelSerializer):

    state = serializers.ReadOnlyField(source='get_state_display')
    items = NestedOrderItemSerializer(many=True)

    class Meta(object):
        model = models.Order
        fields = ('url', 'uuid',
                  'created', 'created_by', 'created_by_username', 'created_by_full_name',
                  'approved_by', 'approved_at', 'approved_by_username', 'approved_by_full_name',
                  'project', 'state', 'items', 'total_cost', 'file')
        read_only_fields = ('created_by', 'approved_by', 'approved_at', 'state', 'total_cost')
        protected_fields = ('project', 'items')
        related_paths = {
            'created_by': ('username', 'full_name'),
            'approved_by': ('username', 'full_name'),
        }
        extra_kwargs = {
            'url': {'lookup_field': 'uuid'},
            'created_by': {'lookup_field': 'uuid', 'view_name': 'user-detail'},
            'approved_by': {'lookup_field': 'uuid', 'view_name': 'user-detail'},
            'project': {'lookup_field': 'uuid', 'view_name': 'project-detail'},
        }

    file = serializers.SerializerMethodField()

    def get_file(self, obj):
        if not obj.has_file():
            return None

        return reverse('marketplace-order-pdf',
                       kwargs={'uuid': obj.uuid},
                       request=self.context['request'])

    @transaction.atomic
    def create(self, validated_data):
        request = self.context['request']
        project = validated_data['project']
        items = [
            models.OrderItem(
                offering=item['offering'],
                plan=item.get('plan'),
                attributes=item.get('attributes', {}),
                limits=item.get('limits', {}),
                type=item.get('type'),
            )
            for item in validated_data['items']
        ]
        return create_order(project, request.user, items, request)

    def get_filtered_field_names(self):
        return 'project',

    def validate_items(self, items):
        for item in items:
            offering = item['offering']

            if offering.shared and offering.terms_of_service and not item.get('accepting_terms_of_service'):
                raise ValidationError(
                    {'items': _('Terms of service for offering \'%s\' have not been accepted.') % offering}
                )

        return items


class CustomerOfferingSerializer(serializers.HyperlinkedModelSerializer):
    offering_set = serializers.HyperlinkedRelatedField(
        many=True,
        view_name='marketplace-offering-detail',
        lookup_field='uuid',
        queryset=models.Offering.objects.all()
    )

    class Meta(object):
        model = structure_models.Customer
        fields = ('offering_set',)


class ResourceSerializer(BaseItemSerializer):
    class Meta(BaseItemSerializer.Meta):
        model = models.Resource
        fields = BaseItemSerializer.Meta.fields + (
            'scope', 'state', 'resource_uuid', 'resource_type',
            'project', 'project_uuid', 'project_name',
            'customer_uuid', 'customer_name',
            'offering_uuid', 'offering_name',
            'backend_metadata', 'is_usage_based', 'name',
            'current_usages',
        )
        read_only_fields = ('backend_metadata', 'scope', 'current_usages')

    state = serializers.ReadOnlyField(source='get_state_display')
    scope = core_serializers.GenericRelatedField()
    resource_uuid = serializers.ReadOnlyField(source='backend_uuid')
    resource_type = serializers.ReadOnlyField(source='backend_type')
    project = serializers.HyperlinkedRelatedField(
        lookup_field='uuid',
        view_name='project-detail',
        read_only=True,
    )
    project_uuid = serializers.ReadOnlyField(source='project.uuid')
    project_name = serializers.ReadOnlyField(source='project.name')
    customer_uuid = serializers.ReadOnlyField(source='project.customer.uuid')
    customer_name = serializers.ReadOnlyField(source='project.customer.name')
    offering_uuid = serializers.ReadOnlyField(source='offering.uuid')
    offering_name = serializers.ReadOnlyField(source='offering.name')
    # If resource is usage-based, frontend would render button to show and report usage
    is_usage_based = serializers.ReadOnlyField(source='offering.is_usage_based')


class ResourceSwitchPlanSerializer(serializers.HyperlinkedModelSerializer):
    class Meta(object):
        model = models.Resource
        fields = ('plan',)

    plan = serializers.HyperlinkedRelatedField(
        view_name='marketplace-plan-detail',
        lookup_field='uuid',
        queryset=models.Plan.objects.all(),
        required=True,
    )

    def validate(self, attrs):
        plan = attrs['plan']
        resource = self.context['view'].get_object()

        if plan.offering != resource.offering:
            raise rf_exceptions.ValidationError({
                'plan': _('Plan is not available for this offering.')
            })

        validate_plan(plan)
        return attrs


class ResourceUpdateLimitsSerializer(serializers.ModelSerializer):
    class Meta(object):
        model = models.Resource
        fields = ('limits',)

    limits = serializers.DictField(child=serializers.IntegerField(min_value=0), required=True)


class BaseComponentSerializer(serializers.Serializer):
    type = serializers.ReadOnlyField(source='component.type')
    name = serializers.ReadOnlyField(source='component.name')
    measured_unit = serializers.ReadOnlyField(source='component.measured_unit')


class CategoryComponentUsageSerializer(core_serializers.RestrictedSerializerMixin,
                                       BaseComponentSerializer,
                                       serializers.ModelSerializer):
    category_title = serializers.ReadOnlyField(source='component.category.title')
    category_uuid = serializers.ReadOnlyField(source='component.category.uuid')
    scope = GenericRelatedField(related_models=(structure_models.Project, structure_models.Customer))

    class Meta(object):
        model = models.CategoryComponentUsage
        fields = ('name', 'type', 'measured_unit', 'category_title', 'category_uuid',
                  'date', 'reported_usage', 'fixed_usage', 'scope')


class BaseComponentUsageSerializer(BaseComponentSerializer, serializers.ModelSerializer):
    class Meta(object):
        model = models.ComponentUsage
        fields = (
            'uuid', 'created', 'description',
            'type', 'name', 'measured_unit', 'usage', 'date',
        )


class ComponentUsageSerializer(BaseComponentUsageSerializer):
    resource_name = serializers.ReadOnlyField(source='resource.name')
    resource_uuid = serializers.ReadOnlyField(source='resource.uuid')

    offering_name = serializers.ReadOnlyField(source='resource.offering.name')
    offering_uuid = serializers.ReadOnlyField(source='resource.offering.uuid')

    project_name = serializers.ReadOnlyField(source='resource.project.name')
    project_uuid = serializers.ReadOnlyField(source='resource.project.uuid')

    customer_name = serializers.ReadOnlyField(source='resource.project.customer.name')
    customer_uuid = serializers.ReadOnlyField(source='resource.project.customer.uuid')

    class Meta(BaseComponentUsageSerializer.Meta):
        fields = BaseComponentUsageSerializer.Meta.fields + (
            'resource_name', 'resource_uuid',
            'offering_name', 'offering_uuid',
            'project_name', 'project_uuid',
            'customer_name', 'customer_uuid',
        )


class ResourcePlanPeriodSerializer(serializers.ModelSerializer):
    class Meta:
        model = models.ResourcePlanPeriod
        fields = ('uuid', 'plan_name', 'plan_uuid', 'start', 'end', 'components')

    plan_name = serializers.ReadOnlyField(source='plan.name')
    plan_uuid = serializers.ReadOnlyField(source='plan.uuid')
    components = BaseComponentUsageSerializer(source='current_components', many=True)


class ImportResourceSerializer(serializers.Serializer):
    backend_id = serializers.CharField()
    project = serializers.SlugRelatedField(queryset=structure_models.Project.objects.all(), slug_field='uuid')
    plan = serializers.SlugRelatedField(queryset=models.Plan.objects.all(), slug_field='uuid')

    def get_fields(self):
        fields = super(ImportResourceSerializer, self).get_fields()

        request = self.context['request']
        user = request.user
        fields['project'].queryset = filter_queryset_for_user(fields['project'].queryset, user)
        return fields


class ServiceProviderSignatureSerializer(serializers.Serializer):
    customer = serializers.SlugRelatedField(queryset=structure_models.Customer.objects.all(), slug_field='uuid')
    data = serializers.CharField()
    dry_run = serializers.BooleanField(default=False, required=False)

    def validate(self, attrs):
        customer = attrs['customer']
        service_provider = getattr(customer, 'serviceprovider', None)
        api_secret_code = service_provider and service_provider.api_secret_code

        if not api_secret_code:
            raise rf_exceptions.ValidationError(_('API secret code is not set.'))

        try:
            data = core_utils.decode_jwt_token(attrs['data'], api_secret_code)
            attrs['data'] = data
            return attrs
        except jwt.exceptions.DecodeError:
            raise rf_exceptions.ValidationError(_('Signature verification failed.'))


class ComponentUsageItemSerializer(serializers.Serializer):
    type = serializers.CharField()
    amount = serializers.IntegerField()
    description = serializers.CharField(required=False, allow_blank=True)


class ComponentUsageCreateSerializer(serializers.Serializer):
    usages = ComponentUsageItemSerializer(many=True)
    plan_period = serializers.SlugRelatedField(queryset=models.ResourcePlanPeriod.objects.all(), slug_field='uuid')

    def validate_plan_period(self, plan_period):
        date = datetime.date.today()
        if plan_period.end and plan_period.end < core_utils.month_start(date):
            raise serializers.ValidationError(_('Billing period is closed.'))
        return plan_period

    def validate(self, attrs):
        attrs = super(ComponentUsageCreateSerializer, self).validate(attrs)
        plan_period = attrs['plan_period']
        resource = plan_period.resource
        offering = resource.plan.offering

        States = models.Resource.States
        if resource.state not in (States.OK, States.UPDATING, States.TERMINATING):
            raise rf_exceptions.ValidationError({
                'resource': _('Resource is not in valid state.')
            })

        valid_components = set(offering.get_usage_components().keys())
        actual_components = {usage['type'] for usage in attrs['usages']}

        missing_components = ', '.join(sorted(valid_components - actual_components))
        invalid_components = ', '.join(sorted(actual_components - valid_components))

        if invalid_components:
            raise rf_exceptions.ValidationError(_('These components are invalid: %s.') % invalid_components)

        if missing_components:
            raise rf_exceptions.ValidationError(_('These components are missing: %s.') % missing_components)

        return attrs

    def save(self):
        plan_period = self.validated_data['plan_period']
        resource = plan_period.resource
        components = resource.plan.offering.get_usage_components()
        now = timezone.now()
        billing_period = core_utils.month_start(now)

        for usage in self.validated_data['usages']:
            amount = usage['amount']
            description = usage.get('description', '')
            component = components[usage['type']]
            component.validate_amount(resource, amount, now)

            models.ComponentUsage.objects.update_or_create(
                resource=resource,
                component=component,
                plan_period=plan_period,
                billing_period=billing_period,
                defaults={'usage': amount, 'date': now, 'description': description},
            )


class OfferingFileSerializer(ProtectedMediaSerializerMixin,
                             core_serializers.RestrictedSerializerMixin,
                             core_serializers.AugmentedSerializerMixin,
                             serializers.HyperlinkedModelSerializer):

    class Meta(object):
        model = models.OfferingFile
        fields = ('url', 'uuid', 'name', 'offering', 'created', 'file',)
        extra_kwargs = dict(
            url={'lookup_field': 'uuid'},
            offering={'lookup_field': 'uuid', 'view_name': 'marketplace-offering-detail'},
        )


def validate_plan(plan):
    """"
    Ensure that maximum amount of resources with current plan is not reached yet.
    """
    if not plan.is_active:
        raise rf_exceptions.ValidationError({
            'plan': _('Plan is not available because limit has been reached.')
        })


def get_is_service_provider(serializer, scope):
    customer = structure_permissions._get_customer(scope)
    return models.ServiceProvider.objects.filter(customer=customer).exists()


def add_service_provider(sender, fields, **kwargs):
    fields['is_service_provider'] = serializers.SerializerMethodField()
    setattr(sender, 'get_is_service_provider', get_is_service_provider)


def get_marketplace_offering_uuid(serializer, scope):
    try:
        return models.Resource.objects.get(scope=scope).offering.uuid
    except ObjectDoesNotExist:
        return


def get_marketplace_offering_name(serializer, scope):
    try:
        return models.Resource.objects.get(scope=scope).offering.name
    except ObjectDoesNotExist:
        return


def get_marketplace_category_uuid(serializer, scope):
    try:
        return models.Resource.objects.get(scope=scope).offering.category.uuid
    except ObjectDoesNotExist:
        return


def get_marketplace_category_name(serializer, scope):
    try:
        return models.Resource.objects.get(scope=scope).offering.category.title
    except ObjectDoesNotExist:
        return


def get_marketplace_resource_uuid(serializer, scope):
    try:
        return models.Resource.objects.get(scope=scope).uuid
    except ObjectDoesNotExist:
        return


def get_marketplace_resource_state(serializer, scope):
    try:
        return models.Resource.objects.get(scope=scope).get_state_display()
    except ObjectDoesNotExist:
        return


def get_is_usage_based(serializer, scope):
    try:
        return models.Resource.objects.get(scope=scope).offering.is_usage_based
    except ObjectDoesNotExist:
        return


def add_marketplace_offering(sender, fields, **kwargs):
    fields['marketplace_offering_uuid'] = serializers.SerializerMethodField()
    setattr(sender, 'get_marketplace_offering_uuid', get_marketplace_offering_uuid)

    fields['marketplace_offering_name'] = serializers.SerializerMethodField()
    setattr(sender, 'get_marketplace_offering_name', get_marketplace_offering_name)

    fields['marketplace_category_uuid'] = serializers.SerializerMethodField()
    setattr(sender, 'get_marketplace_category_uuid', get_marketplace_category_uuid)

    fields['marketplace_category_name'] = serializers.SerializerMethodField()
    setattr(sender, 'get_marketplace_category_name', get_marketplace_category_name)

    fields['marketplace_resource_uuid'] = serializers.SerializerMethodField()
    setattr(sender, 'get_marketplace_resource_uuid', get_marketplace_resource_uuid)

    fields['marketplace_resource_state'] = serializers.SerializerMethodField()
    setattr(sender, 'get_marketplace_resource_state', get_marketplace_resource_state)

    fields['is_usage_based'] = serializers.SerializerMethodField()
    setattr(sender, 'get_is_usage_based', get_is_usage_based)


class ResourceTerminateSerializer(serializers.Serializer):
    attributes = serializers.JSONField(label=_('Termination attributes'), required=False)


core_signals.pre_serializer_fields.connect(
    sender=structure_serializers.CustomerSerializer,
    receiver=add_service_provider,
)

core_signals.pre_serializer_fields.connect(
    sender=support_serializers.OfferingSerializer,
    receiver=add_marketplace_offering,
)

for resource_serializer in SupportedServices.get_resource_serializers():
    core_signals.pre_serializer_fields.connect(
        sender=resource_serializer,
        receiver=add_marketplace_offering,
    )
