from django.core.exceptions import ValidationError
from django.db.models import Count
from django.utils.translation import gettext_lazy as _

from core.models import ObjectChange
from core.tables import ObjectChangeTable
from netbox.object_actions import AddObject, BulkDelete, BulkEdit, BulkExport, BulkImport, BulkRename
from netbox.ui import actions, layout
from netbox.ui.panels import (
    ContextTablePanel,
    JSONPanel,
    ObjectsTablePanel,
    OrganizationalObjectPanel,
    RelatedObjectsPanel,
    TemplatePanel,
)
from netbox.views import generic
from users.ui import panels
from users.utils import user_may_grant_token
from utilities.query import count_related
from utilities.views import GetRelatedModelsMixin, register_model_view

from . import filtersets, forms, tables
from .models import Group, ObjectPermission, Owner, OwnerGroup, Token, User

#
# Tokens
#


@register_model_view(Token, 'list', path='', detail=False)
class TokenListView(generic.ObjectListView):
    queryset = Token.objects.all()
    filterset = filtersets.TokenFilterSet
    filterset_form = forms.TokenFilterForm
    table = tables.TokenTable
    actions = (AddObject, BulkImport, BulkExport, BulkEdit, BulkDelete)


@register_model_view(Token)
class TokenView(generic.ObjectView):
    queryset = Token.objects.all()
    layout = layout.SimpleLayout(
        left_panels=[
            panels.TokenPanel(),
        ],
        right_panels=[
            panels.TokenExamplePanel(),
        ],
    )

    def get_extra_context(self, request, instance):
        # Pop a one-time plaintext value (set by TokenEditView.post_save when a token is first created) and assemble
        # the full HTTP authorization string for display. The plaintext is never persisted; popping ensures the
        # banner only renders once.
        plaintext = request.session.pop(f'_token_plaintext_{instance.pk}', None)
        if plaintext:
            return {'token_auth_string': f'{instance.get_auth_header_prefix()}{plaintext}'}
        return {}


@register_model_view(Token, 'add', detail=False)
@register_model_view(Token, 'edit')
class TokenEditView(generic.ObjectEditView):
    queryset = Token.objects.all()
    form = forms.TokenForm
    template_name = 'users/token_edit.html'

    def alter_object(self, obj, request, url_args, url_kwargs):
        # Attach the request so that UserTokenForm.save() can stash the newly-generated plaintext on the session.
        obj._request = request
        return obj


@register_model_view(Token, 'delete')
class TokenDeleteView(generic.ObjectDeleteView):
    queryset = Token.objects.all()


@register_model_view(Token, 'bulk_import', path='import', detail=False)
class TokenBulkImportView(generic.BulkImportView):
    queryset = Token.objects.all()
    model_form = forms.TokenImportForm

    def save_object(self, object_form, request):
        # Creating a Token on behalf of another user requires the grant_token permission. This is enforced only on
        # creation; TokenImportForm disables the user field on update, so an existing Token's owner cannot change.
        token_user = object_form.cleaned_data.get('user')
        if object_form.instance._state.adding and token_user and token_user != request.user \
                and not user_may_grant_token(request.user, token_user):
            raise ValidationError(_("This user does not have permission to create tokens for other users."))
        return super().save_object(object_form, request)


@register_model_view(Token, 'bulk_edit', path='edit', detail=False)
class TokenBulkEditView(generic.BulkEditView):
    queryset = Token.objects.all()
    table = tables.TokenTable
    form = forms.TokenBulkEditForm


@register_model_view(Token, 'bulk_delete', path='delete', detail=False)
class TokenBulkDeleteView(generic.BulkDeleteView):
    queryset = Token.objects.all()
    table = tables.TokenTable


#
# Users
#

@register_model_view(User, 'list', path='', detail=False)
class UserListView(generic.ObjectListView):
    queryset = User.objects.all()
    filterset = filtersets.UserFilterSet
    filterset_form = forms.UserFilterForm
    table = tables.UserTable


@register_model_view(User)
class UserView(generic.ObjectView):
    queryset = User.objects.all()
    layout = layout.SimpleLayout(
        left_panels=[
            panels.UserPanel(),
        ],
        right_panels=[
            ObjectsTablePanel(
                'users.Group', title=_('Assigned Groups'), filters={'user_id': lambda ctx: ctx['object'].pk}
            ),
            ObjectsTablePanel(
                'users.ObjectPermission',
                title=_('Assigned Permissions'),
                filters={'user_id': lambda ctx: ctx['object'].pk},
            ),
            ObjectsTablePanel(
                'users.Owner', title=_('Owner Membership'), filters={'user_id': lambda ctx: ctx['object'].pk}
            ),
        ],
        bottom_panels=[
            ContextTablePanel(
                'changelog_table',
                title=_('Recent Activity'),
                actions=[
                    actions.LinkAction(
                        view_name='core:objectchange_list',
                        url_params={'user_id': lambda ctx: ctx['object'].pk},
                        label=_('View All'),
                        button_icon='arrow-right-thick',
                        permissions=['core.view_objectchange'],
                    ),
                ],
            ),
        ],
    )

    def get_extra_context(self, request, instance):
        changelog = ObjectChange.objects.valid_models().restrict(request.user, 'view').filter(user=instance)[:20]
        changelog_table = ObjectChangeTable(changelog)
        changelog_table.orderable = False
        changelog_table.configure(request)

        return {
            'changelog_table': changelog_table,
        }


@register_model_view(User, 'add', detail=False)
@register_model_view(User, 'edit')
class UserEditView(generic.ObjectEditView):
    queryset = User.objects.all()
    form = forms.UserForm


@register_model_view(User, 'delete')
class UserDeleteView(generic.ObjectDeleteView):
    queryset = User.objects.all()


@register_model_view(User, 'bulk_import', path='import', detail=False)
class UserBulkImportView(generic.BulkImportView):
    queryset = User.objects.all()
    model_form = forms.UserImportForm


@register_model_view(User, 'bulk_edit', path='edit', detail=False)
class UserBulkEditView(generic.BulkEditView):
    queryset = User.objects.all()
    filterset = filtersets.UserFilterSet
    table = tables.UserTable
    form = forms.UserBulkEditForm


@register_model_view(User, 'bulk_rename', path='rename', detail=False)
class UserBulkRenameView(generic.BulkRenameView):
    queryset = User.objects.all()
    field_name = 'username'
    filterset = filtersets.UserFilterSet


@register_model_view(User, 'bulk_delete', path='delete', detail=False)
class UserBulkDeleteView(generic.BulkDeleteView):
    queryset = User.objects.all()
    filterset = filtersets.UserFilterSet
    table = tables.UserTable


#
# Groups
#

@register_model_view(Group, 'list', path='', detail=False)
class GroupListView(generic.ObjectListView):
    queryset = Group.objects.annotate(users_count=Count('user')).order_by('name')
    filterset = filtersets.GroupFilterSet
    filterset_form = forms.GroupFilterForm
    table = tables.GroupTable


@register_model_view(Group)
class GroupView(generic.ObjectView):
    queryset = Group.objects.all()
    layout = layout.SimpleLayout(
        left_panels=[
            OrganizationalObjectPanel(),
        ],
        right_panels=[
            ObjectsTablePanel(
                'users.User',
                filters={'group_id': lambda ctx: ctx['object'].pk},
            ),
            ObjectsTablePanel(
                'users.ObjectPermission',
                title=_('Assigned Permissions'),
                filters={'group_id': lambda ctx: ctx['object'].pk},
            ),
            ObjectsTablePanel(
                'users.Owner', title=_('Owner Membership'), filters={'user_group_id': lambda ctx: ctx['object'].pk}
            ),
        ],
    )


@register_model_view(Group, 'add', detail=False)
@register_model_view(Group, 'edit')
class GroupEditView(generic.ObjectEditView):
    queryset = Group.objects.all()
    form = forms.GroupForm


@register_model_view(Group, 'delete')
class GroupDeleteView(generic.ObjectDeleteView):
    queryset = Group.objects.all()


@register_model_view(Group, 'bulk_import', path='import', detail=False)
class GroupBulkImportView(generic.BulkImportView):
    queryset = Group.objects.all()
    model_form = forms.GroupImportForm


@register_model_view(Group, 'bulk_edit', path='edit', detail=False)
class GroupBulkEditView(generic.BulkEditView):
    queryset = Group.objects.all()
    filterset = filtersets.GroupFilterSet
    table = tables.GroupTable
    form = forms.GroupBulkEditForm


@register_model_view(Group, 'bulk_rename', path='rename', detail=False)
class GroupBulkRenameView(generic.BulkRenameView):
    queryset = Group.objects.all()
    filterset = filtersets.GroupFilterSet


@register_model_view(Group, 'bulk_delete', path='delete', detail=False)
class GroupBulkDeleteView(generic.BulkDeleteView):
    queryset = Group.objects.annotate(users_count=Count('user')).order_by('name')
    filterset = filtersets.GroupFilterSet
    table = tables.GroupTable


#
# ObjectPermissions
#

@register_model_view(ObjectPermission, 'list', path='', detail=False)
class ObjectPermissionListView(generic.ObjectListView):
    queryset = ObjectPermission.objects.all()
    filterset = filtersets.ObjectPermissionFilterSet
    filterset_form = forms.ObjectPermissionFilterForm
    table = tables.ObjectPermissionTable
    actions = (AddObject, BulkExport, BulkEdit, BulkRename, BulkDelete)


@register_model_view(ObjectPermission)
class ObjectPermissionView(generic.ObjectView):
    queryset = ObjectPermission.objects.all()
    layout = layout.SimpleLayout(
        left_panels=[
            panels.ObjectPermissionPanel(),
            panels.ObjectPermissionActionsPanel(),
            JSONPanel('constraints', title=_('Constraints')),
        ],
        right_panels=[
            TemplatePanel('users/panels/object_types.html'),
            ObjectsTablePanel(
                'users.User', title=_('Assigned Users'), filters={'permission_id': lambda ctx: ctx['object'].pk}
            ),
            ObjectsTablePanel(
                'users.Group', title=_('Assigned Groups'), filters={'permission_id': lambda ctx: ctx['object'].pk}
            ),
        ],
    )


@register_model_view(ObjectPermission, 'add', detail=False)
@register_model_view(ObjectPermission, 'edit')
class ObjectPermissionEditView(generic.ObjectEditView):
    queryset = ObjectPermission.objects.all()
    form = forms.ObjectPermissionForm


@register_model_view(ObjectPermission, 'delete')
class ObjectPermissionDeleteView(generic.ObjectDeleteView):
    queryset = ObjectPermission.objects.all()
    filterset = filtersets.ObjectPermissionFilterSet


@register_model_view(ObjectPermission, 'bulk_edit', path='edit', detail=False)
class ObjectPermissionBulkEditView(generic.BulkEditView):
    queryset = ObjectPermission.objects.all()
    filterset = filtersets.ObjectPermissionFilterSet
    table = tables.ObjectPermissionTable
    form = forms.ObjectPermissionBulkEditForm


@register_model_view(ObjectPermission, 'bulk_rename', path='rename', detail=False)
class ObjectPermissionBulkRenameView(generic.BulkRenameView):
    queryset = ObjectPermission.objects.all()


@register_model_view(ObjectPermission, 'bulk_delete', path='delete', detail=False)
class ObjectPermissionBulkDeleteView(generic.BulkDeleteView):
    queryset = ObjectPermission.objects.all()
    filterset = filtersets.ObjectPermissionFilterSet
    table = tables.ObjectPermissionTable


#
# Owner groups
#

@register_model_view(OwnerGroup, 'list', path='', detail=False)
class OwnerGroupListView(generic.ObjectListView):
    queryset = OwnerGroup.objects.annotate(
        owner_count=count_related(Owner, 'group')
    )
    filterset = filtersets.OwnerGroupFilterSet
    filterset_form = forms.OwnerGroupFilterForm
    table = tables.OwnerGroupTable


@register_model_view(OwnerGroup)
class OwnerGroupView(generic.ObjectView):
    queryset = OwnerGroup.objects.all()
    layout = layout.SimpleLayout(
        left_panels=[
            OrganizationalObjectPanel(),
        ],
        right_panels=[
            ObjectsTablePanel(
                'users.Owner',
                filters={'group_id': lambda ctx: ctx['object'].pk},
                title=_('Members'),
                exclude_columns=['group'],
                actions=[
                    actions.AddObject(
                        'users.Owner',
                        url_params={'group': lambda ctx: ctx['object'].pk},
                    ),
                ],
            ),
        ],
    )


@register_model_view(OwnerGroup, 'add', detail=False)
@register_model_view(OwnerGroup, 'edit')
class OwnerGroupEditView(generic.ObjectEditView):
    queryset = OwnerGroup.objects.all()
    form = forms.OwnerGroupForm


@register_model_view(OwnerGroup, 'delete')
class OwnerGroupDeleteView(generic.ObjectDeleteView):
    queryset = OwnerGroup.objects.all()


@register_model_view(OwnerGroup, 'bulk_import', path='import', detail=False)
class OwnerGroupBulkImportView(generic.BulkImportView):
    queryset = OwnerGroup.objects.all()
    model_form = forms.OwnerGroupImportForm


@register_model_view(OwnerGroup, 'bulk_edit', path='edit', detail=False)
class OwnerGroupBulkEditView(generic.BulkEditView):
    queryset = OwnerGroup.objects.all()
    filterset = filtersets.OwnerGroupFilterSet
    table = tables.OwnerGroupTable
    form = forms.OwnerGroupBulkEditForm


@register_model_view(OwnerGroup, 'bulk_rename', path='rename', detail=False)
class OwnerGroupBulkRenameView(generic.BulkRenameView):
    queryset = OwnerGroup.objects.all()


@register_model_view(OwnerGroup, 'bulk_delete', path='delete', detail=False)
class OwnerGroupBulkDeleteView(generic.BulkDeleteView):
    queryset = OwnerGroup.objects.all()
    filterset = filtersets.OwnerGroupFilterSet
    table = tables.OwnerGroupTable


#
# Owners
#

@register_model_view(Owner, 'list', path='', detail=False)
class OwnerListView(generic.ObjectListView):
    queryset = Owner.objects.all()
    filterset = filtersets.OwnerFilterSet
    filterset_form = forms.OwnerFilterForm
    table = tables.OwnerTable


@register_model_view(Owner)
class OwnerView(GetRelatedModelsMixin, generic.ObjectView):
    queryset = Owner.objects.all()
    layout = layout.SimpleLayout(
        left_panels=[
            panels.OwnerPanel(),
            ObjectsTablePanel(
                'users.Group',
                filters={'owner_id': lambda ctx: ctx['object'].pk},
            ),
            ObjectsTablePanel(
                'users.User',
                filters={'owner_id': lambda ctx: ctx['object'].pk},
            ),
        ],
        right_panels=[
            RelatedObjectsPanel(),
        ],
    )

    def get_extra_context(self, request, instance):
        return {
            'related_models': self.get_related_models(
                request,
                instance,
                omit=(Group, User),
            ),
        }


@register_model_view(Owner, 'add', detail=False)
@register_model_view(Owner, 'edit')
class OwnerEditView(generic.ObjectEditView):
    queryset = Owner.objects.all()
    form = forms.OwnerForm


@register_model_view(Owner, 'delete')
class OwnerDeleteView(generic.ObjectDeleteView):
    queryset = Owner.objects.all()


@register_model_view(Owner, 'bulk_import', path='import', detail=False)
class OwnerBulkImportView(generic.BulkImportView):
    queryset = Owner.objects.all()
    model_form = forms.OwnerImportForm


@register_model_view(Owner, 'bulk_edit', path='edit', detail=False)
class OwnerBulkEditView(generic.BulkEditView):
    queryset = Owner.objects.all()
    filterset = filtersets.OwnerFilterSet
    table = tables.OwnerTable
    form = forms.OwnerBulkEditForm


@register_model_view(Owner, 'bulk_rename', path='rename', detail=False)
class OwnerBulkRenameView(generic.BulkRenameView):
    queryset = Owner.objects.all()


@register_model_view(Owner, 'bulk_delete', path='delete', detail=False)
class OwnerBulkDeleteView(generic.BulkDeleteView):
    queryset = Owner.objects.all()
    filterset = filtersets.OwnerFilterSet
    table = tables.OwnerTable
