from django.db.models import ForeignKey
from django.template import loader
from django.urls.exceptions import NoReverseMatch
from django.utils.translation import gettext_lazy as _

from core.models import ObjectType
from extras.models import ExportTemplate
from utilities.querydict import prepare_cloned_fields
from utilities.views import get_action_url

__all__ = (
    'AddObject',
    'BulkDelete',
    'BulkEdit',
    'BulkExport',
    'BulkImport',
    'BulkRename',
    'CloneObject',
    'DeleteObject',
    'EditObject',
    'ObjectAction',
)


class ObjectAction:
    """
    Base class for single- and multi-object operations.

    Params:
        name: The action name appended to the module for view resolution
        label: Human-friendly label for the rendered button
        template_name: Name of the HTML template which renders the button
        multi: Set to True if this action is performed by selecting multiple objects (i.e. using a table)
        permissions_required: The set of permissions a user must have to perform the action
        url_kwargs: The set of URL keyword arguments to pass when resolving the view's URL
    """
    name = ''
    label = None
    template_name = None
    multi = False
    permissions_required = set()
    url_kwargs = []

    @classmethod
    def get_url(cls, obj):
        kwargs = {
            kwarg: getattr(obj, kwarg) for kwarg in cls.url_kwargs
        }
        try:
            return get_action_url(obj, action=cls.name, kwargs=kwargs)
        except NoReverseMatch:
            return None

    @classmethod
    def get_url_params(cls, context):
        request = context['request']
        params = request.GET.copy()
        if 'return_url' in context:
            params['return_url'] = context['return_url']
        return params

    @classmethod
    def get_context(cls, context, obj):
        """
        Return any additional context data needed to render the button.
        """
        return {}

    @classmethod
    def render(cls, context, obj, **kwargs):
        ctx = {
            'perms': context['perms'],
            'request': context['request'],
            'url': cls.get_url(obj),
            'url_params': cls.get_url_params(context),
            'label': cls.label,
            **cls.get_context(context, obj),
            **kwargs,
        }
        return loader.render_to_string(cls.template_name, ctx)


class AddObject(ObjectAction):
    """
    Create a new object.
    """
    name = 'add'
    label = _('Add')
    permissions_required = {'add'}
    template_name = 'buttons/add.html'


class CloneObject(ObjectAction):
    """
    Populate the new object form with select details from an existing object.
    """
    name = 'add'
    label = _('Clone')
    permissions_required = {'add'}
    template_name = 'buttons/clone.html'

    @classmethod
    def get_url(cls, obj):
        url = super().get_url(obj)
        param_string = prepare_cloned_fields(obj).urlencode()
        return f'{url}?{param_string}' if param_string else None


class EditObject(ObjectAction):
    """
    Edit a single object.
    """
    name = 'edit'
    label = _('Edit')
    permissions_required = {'change'}
    url_kwargs = ['pk']
    template_name = 'buttons/edit.html'


class DeleteObject(ObjectAction):
    """
    Delete a single object.
    """
    name = 'delete'
    label = _('Delete')
    permissions_required = {'delete'}
    url_kwargs = ['pk']
    template_name = 'buttons/delete.html'


class BulkImport(ObjectAction):
    """
    Import multiple objects at once.
    """
    name = 'bulk_import'
    label = _('Import')
    permissions_required = {'add'}
    template_name = 'buttons/import.html'


class BulkExport(ObjectAction):
    """
    Export multiple objects at once.
    """
    name = 'export'
    label = _('Export')
    permissions_required = {'view'}
    template_name = 'buttons/export.html'

    @classmethod
    def get_context(cls, context, model):
        object_type = ObjectType.objects.get_for_model(model)
        user = context['request'].user

        # Determine if the "all data" export returns CSV or YAML
        data_format = 'YAML' if hasattr(object_type.model_class(), 'to_yaml') else 'CSV'

        # Retrieve all export templates for this model
        export_templates = ExportTemplate.objects.restrict(user, 'view').filter(object_types=object_type)

        return {
            'object_type': object_type,
            'url_params': context['request'].GET.urlencode() if context['request'].GET else '',
            'export_templates': export_templates,
            'data_format': data_format,
        }


class BulkEdit(ObjectAction):
    """
    Change the value of one or more fields on a set of objects.
    """
    name = 'bulk_edit'
    label = _('Edit Selected')
    multi = True
    permissions_required = {'change'}
    template_name = 'buttons/bulk_edit.html'

    @classmethod
    def get_context(cls, context, model):
        url_params = super().get_url_params(context)

        # If this is a child object, pass the parent's PK as a URL parameter
        if parent := context.get('object'):
            for field in model._meta.get_fields():
                if isinstance(field, ForeignKey) and field.remote_field.model == parent.__class__:
                    url_params[field.name] = parent.pk
                    break

        return {
            'url_params': url_params,
        }


class BulkRename(ObjectAction):
    """
    Rename multiple objects at once.
    """
    name = 'bulk_rename'
    label = _('Rename Selected')
    multi = True
    permissions_required = {'change'}
    template_name = 'buttons/bulk_rename.html'


class BulkDelete(ObjectAction):
    """
    Delete each of a set of objects.
    """
    name = 'bulk_delete'
    label = _('Delete Selected')
    multi = True
    permissions_required = {'delete'}
    template_name = 'buttons/bulk_delete.html'
