from rest_framework.decorators import action
from rest_framework.renderers import JSONRenderer
from rest_framework.response import Response
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_500_INTERNAL_SERVER_ERROR

from netbox.api.authentication import TokenWritePermission
from netbox.api.renderers import TextRenderer

from .serializers import ConfigTemplateSerializer

__all__ = (
    'ConfigContextQuerySetMixin',
    'ConfigTemplateRenderMixin',
    'RenderConfigMixin',
)


class ConfigContextQuerySetMixin:
    """
    Used by views that work with config context models (device and virtual machine).
    Provides a get_queryset() method which deals with adding the config context
    data annotation or not.
    """
    def get_queryset(self):
        """
        Build the proper queryset based on the request context

        If the `brief` query param equates to True or the `exclude` query param
        includes `config_context` as a value, return the base queryset.

        Else, return the queryset annotated with config context data
        """
        queryset = super().get_queryset()
        request = self.get_serializer_context()['request']
        if self.brief or 'config_context' in request.query_params.get('exclude', []):
            return queryset
        return queryset.annotate_config_context_data()


class ConfigTemplateRenderMixin:
    """
    Provides a method to return a rendered ConfigTemplate as REST API data.
    """
    def render_configtemplate(self, request, configtemplate, context):
        try:
            output = configtemplate.render(context=context)
        except Exception as e:
            detail = configtemplate.format_render_error(e)
            if request.accepted_renderer.format == 'txt':
                return Response(detail, status=HTTP_500_INTERNAL_SERVER_ERROR)
            return Response({'detail': detail}, status=HTTP_500_INTERNAL_SERVER_ERROR)

        # If the client has requested "text/plain", return the raw content.
        if request.accepted_renderer.format == 'txt':
            return Response(output)

        template_serializer = ConfigTemplateSerializer(configtemplate, nested=True, context={'request': request})

        return Response({
            'configtemplate': template_serializer.data,
            'content': output
        })


class RenderConfigMixin(ConfigTemplateRenderMixin):
    """
    Provides a /render-config/ endpoint for REST API views whose model may have a ConfigTemplate assigned.
    """

    def get_permissions(self):
        # For render_config action, check only token write ability (not model permissions)
        if self.action == 'render_config':
            return [TokenWritePermission()]
        return super().get_permissions()

    @action(detail=True, methods=['post'], url_path='render-config', renderer_classes=[JSONRenderer, TextRenderer])
    def render_config(self, request, pk):
        """
        Resolve and render the preferred ConfigTemplate for this Device.
        """
        # Override restrict() on the default queryset to enforce the render_config & view actions
        self.queryset = self.queryset.model.objects.restrict(request.user, 'render_config').restrict(
            request.user, 'view'
        )
        instance = self.get_object()

        object_type = instance._meta.model_name
        configtemplate = instance.get_config_template()
        if not configtemplate:
            return Response({
                'error': f'No config template found for this {object_type}.'
            }, status=HTTP_400_BAD_REQUEST)

        # Compile context data
        context_data = instance.get_config_context()
        context_data.update(request.data)
        context_data.update({object_type: instance})

        return self.render_configtemplate(request, configtemplate, context_data)
