from django.contrib import messages
from django.contrib.contenttypes.models import ContentType
from django.db.models import Count, Q
from django.shortcuts import redirect, render
from django.utils.translation import gettext_lazy as _

from core.choices import ObjectChangeActionChoices
from core.filtersets import ObjectChangeFilterSet
from core.models import ObjectChange
from netbox.views import generic
from utilities.views import ViewTab, register_model_view
from . import filtersets, forms, tables
from .choices import BranchStatusChoices
from .jobs import MergeBranchJob, RevertBranchJob, SyncBranchJob
from .models import ChangeDiff, Branch


#
# Branches
#

class BranchListView(generic.ObjectListView):
    queryset = Branch.objects.annotate(
        # Annotate the number of associated ChangeDiffs with conflicts
        conflicts=Count('changediff', filter=Q(changediff__conflicts__isnull=False))
    ).order_by('name')
    filterset = filtersets.BranchFilterSet
    filterset_form = forms.BranchFilterForm
    table = tables.BranchTable


@register_model_view(Branch)
class BranchView(generic.ObjectView):
    queryset = Branch.objects.all()

    def get_extra_context(self, request, instance):
        qs = instance.get_changes().values_list('changed_object_type').annotate(count=Count('pk'))
        if instance.ready or instance.merged:
            stats = {
                'created': {
                    ContentType.objects.get(pk=ct): count
                    for ct, count in qs.filter(action=ObjectChangeActionChoices.ACTION_CREATE)
                },
                'updated': {
                    ContentType.objects.get(pk=ct): count
                    for ct, count in qs.filter(action=ObjectChangeActionChoices.ACTION_UPDATE)
                },
                'deleted': {
                    ContentType.objects.get(pk=ct): count
                    for ct, count in qs.filter(action=ObjectChangeActionChoices.ACTION_DELETE)
                },
            }
        else:
            stats = {}

        latest_change = instance.get_changes().order_by('time').last()
        last_job = instance.jobs.order_by('created').last()

        return {
            'stats': stats,
            'latest_change': latest_change,
            'last_job': last_job,
            'conflicts_count': ChangeDiff.objects.filter(branch=instance, conflicts__isnull=False).count(),
        }


@register_model_view(Branch, 'edit')
class BranchEditView(generic.ObjectEditView):
    queryset = Branch.objects.all()
    form = forms.BranchForm

    def alter_object(self, obj, request, url_args, url_kwargs):
        if not obj.pk:
            obj.owner = request.user
        return obj


@register_model_view(Branch, 'delete')
class BranchDeleteView(generic.ObjectDeleteView):
    queryset = Branch.objects.all()
    default_return_url = 'plugins:netbox_branching:branch_list'


def _get_diff_count(obj):
    return ChangeDiff.objects.filter(branch=obj).count()


@register_model_view(Branch, 'diff')
class BranchDiffView(generic.ObjectChildrenView):
    queryset = Branch.objects.all()
    child_model = ChangeDiff
    filterset = filtersets.ChangeDiffFilterSet
    table = tables.ChangeDiffTable
    actions = {}
    tab = ViewTab(
        label=_('Diff'),
        badge=_get_diff_count,
        permission='netbox_branching.view_branch'
    )

    def get_children(self, request, parent):
        return ChangeDiff.objects.filter(branch=parent)


@register_model_view(Branch, 'changes-behind')
class BranchChangesBehindView(generic.ObjectChildrenView):
    queryset = Branch.objects.all()
    child_model = ObjectChange
    filterset = ObjectChangeFilterSet
    table = tables.ChangesTable
    actions = {}
    tab = ViewTab(
        label=_('Changes Behind'),
        badge=lambda obj: obj.get_unsynced_changes().count(),
        permission='netbox_branching.view_branch'
    )

    def get_children(self, request, parent):
        return parent.get_unsynced_changes().order_by('time')


@register_model_view(Branch, 'changes-ahead')
class BranchChangesAheadView(generic.ObjectChildrenView):
    queryset = Branch.objects.all()
    child_model = ObjectChange
    filterset = ObjectChangeFilterSet
    table = tables.ChangesTable
    actions = {}
    tab = ViewTab(
        label=_('Changes Ahead'),
        badge=lambda obj: obj.get_unmerged_changes().count(),
        permission='netbox_branching.view_branch'
    )

    def get_children(self, request, parent):
        return parent.get_unmerged_changes().order_by('time')


def _get_change_count(obj):
    return obj.get_unmerged_changes().count()


@register_model_view(Branch, 'changes-merged')
class BranchChangesAheadView(generic.ObjectChildrenView):
    queryset = Branch.objects.all()
    child_model = ObjectChange
    filterset = ObjectChangeFilterSet
    table = tables.ChangesTable
    actions = {}
    tab = ViewTab(
        label=_('Changes Merged'),
        badge=lambda obj: obj.get_merged_changes().count(),
        permission='netbox_branching.view_branch',
        hide_if_empty=True
    )

    def get_children(self, request, parent):
        return parent.get_merged_changes().order_by('time')


class BaseBranchActionView(generic.ObjectView):
    """
    Base view for syncing or merging a Branch.
    """
    queryset = Branch.objects.all()
    form = forms.BranchActionForm
    template_name = 'netbox_branching/branch_action.html'
    action = None
    valid_states = (
        BranchStatusChoices.READY,
    )

    def get_required_permission(self):
        return f'netbox_branching.{self.action}_branch'

    @staticmethod
    def _get_conflicts_table(branch):
        conflicts = ChangeDiff.objects.filter(branch=branch, conflicts__isnull=False)
        conflicts_table = tables.ChangeDiffTable(conflicts)
        conflicts_table.columns.show('pk')

        return conflicts_table

    def do_action(self, branch, request, form):
        raise NotImplementedError(f"{self.__class__} must implement action() method.")

    def get(self, request, **kwargs):
        branch = self.get_object(**kwargs)
        form = forms.BranchActionForm(branch)

        return render(request, self.template_name, {
            'branch': branch,
            'action': _(f'{self.action.title()} Branch'),
            'form': form,
            'conflicts_table': self._get_conflicts_table(branch),
        })

    def post(self, request, **kwargs):
        branch = self.get_object(**kwargs)
        form = forms.BranchActionForm(branch, request.POST)

        if branch.status not in self.valid_states:
            messages.error(request, _(
                "The branch must be in one of the following states to perform this action: {valid_states}"
            ).format(valid_states=', '.join(self.valid_states)))
        elif form.is_valid():
            return self.do_action(branch, request, form)

        return render(request, self.template_name, {
            'branch': branch,
            'action': _(f'{self.action.title()} Branch'),
            'form': form,
            'conflicts_table': self._get_conflicts_table(branch),
        })


@register_model_view(Branch, 'sync')
class BranchSyncView(BaseBranchActionView):
    action = 'sync'

    def do_action(self, branch, request, form):
        # Enqueue a background job to sync the Branch
        SyncBranchJob.enqueue(
            instance=branch,
            user=request.user,
            commit=form.cleaned_data['commit']
        )
        messages.success(request, f"Syncing of branch {branch} in progress")

        return redirect(branch.get_absolute_url())


@register_model_view(Branch, 'merge')
class BranchMergeView(BaseBranchActionView):
    action = 'merge'

    def do_action(self, branch, request, form):
        # Enqueue a background job to merge the Branch
        MergeBranchJob.enqueue(
            instance=branch,
            user=request.user,
            commit=form.cleaned_data['commit']
        )
        messages.success(request, f"Merging of branch {branch} in progress")

        return redirect(branch.get_absolute_url())


@register_model_view(Branch, 'revert')
class BranchRevertView(BaseBranchActionView):
    action = 'revert'
    valid_states = (
        BranchStatusChoices.MERGED,
    )

    def do_action(self, branch, request, form):
        # Enqueue a background job to revert the Branch
        RevertBranchJob.enqueue(
            instance=branch,
            user=request.user,
            commit=form.cleaned_data['commit']
        )
        messages.success(request, f"Reverting branch {branch}")

        return redirect(branch.get_absolute_url())


class BranchBulkImportView(generic.BulkImportView):
    queryset = Branch.objects.all()
    model_form = forms.BranchImportForm


class BranchBulkEditView(generic.BulkEditView):
    queryset = Branch.objects.all()
    filterset = filtersets.BranchFilterSet
    table = tables.BranchTable
    form = forms.BranchBulkEditForm


class BranchBulkDeleteView(generic.BulkDeleteView):
    queryset = Branch.objects.all()
    filterset = filtersets.BranchFilterSet
    table = tables.BranchTable


#
# Change diffs
#

class ChangeDiffListView(generic.ObjectListView):
    queryset = ChangeDiff.objects.all()
    filterset = filtersets.ChangeDiffFilterSet
    filterset_form = forms.ChangeDiffFilterForm
    table = tables.ChangeDiffTable
