import json
from operator import attrgetter

from django import forms
from django.contrib.auth.decorators import permission_required
from django.contrib.auth.models import User
from django.core.serializers.json import DjangoJSONEncoder
from django.db import transaction
from django.http import HttpResponse, Http404
from django.shortcuts import get_list_or_404, redirect, render
from django.utils.timezone import now
from django.views.decorators.cache import never_cache

from main.models import Package, Arch, Repo
from ..models import SignoffSpecification, Signoff
from ..utils import (get_signoff_groups, approved_by_signoffs,
        PackageSignoffGroup)

@permission_required('main.change_package')
def signoffs(request):
    signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase'))
    for group in signoff_groups:
        group.user = request.user

    context = {
        'signoff_groups': signoff_groups,
        'arches': Arch.objects.all(),
        'repo_names': sorted({g.target_repo for g in signoff_groups}),
    }
    return render(request, 'packages/signoffs.html', context)

@permission_required('main.change_package')
@never_cache
def signoff_package(request, name, repo, arch, revoke=False):
    packages = get_list_or_404(Package, pkgbase=name,
            arch__name=arch, repo__name__iexact=repo, repo__testing=True)
    package = packages[0]

    spec = SignoffSpecification.objects.get_or_default_from_package(package)

    if revoke:
        try:
            signoff = Signoff.objects.get_from_package(
                    package, request.user, False)
        except Signoff.DoesNotExist:
            raise Http404
        signoff.revoked = now()
        signoff.save(update_fields=('revoked',))
        created = False
    else:
        # ensure we should even be accepting signoffs
        if spec.known_bad or not spec.enabled:
            return render(request, '403.html', status=403)
        signoff, created = Signoff.objects.get_or_create_from_package(
                package, request.user)

    all_signoffs = Signoff.objects.for_package(package)

    if request.is_ajax():
        data = {
            'created': created,
            'revoked': bool(signoff.revoked),
            'approved': approved_by_signoffs(all_signoffs, spec),
            'required': spec.required,
            'enabled': spec.enabled,
            'known_bad': spec.known_bad,
            'user': str(request.user),
        }
        return HttpResponse(json.dumps(data, ensure_ascii=False),
                content_type='application/json')

    return redirect('package-signoffs')

class SignoffOptionsForm(forms.ModelForm):
    apply_all = forms.BooleanField(required=False,
        help_text="Apply these options to all architectures?")

    class Meta:
        model = SignoffSpecification
        fields = ('required', 'enabled', 'known_bad', 'comments')

def _signoff_options_all(request, name, repo):
    seen_ids = set()
    with transaction.atomic():
        # find or create a specification for all architectures, then
        # graft the form data onto them
        packages = Package.objects.filter(pkgbase=name,
            repo__name__iexact=repo, repo__testing=True)
        for package in packages:
            try:
                spec = SignoffSpecification.objects.get_from_package(package)
                if spec.pk in seen_ids:
                    continue
            except SignoffSpecification.DoesNotExist:
                spec = SignoffSpecification(pkgbase=package.pkgbase,
                        pkgver=package.pkgver, pkgrel=package.pkgrel,
                        epoch=package.epoch, arch=package.arch,
                        repo=package.repo)

            if spec.user is None:
                spec.user = request.user

            form = SignoffOptionsForm(request.POST, instance=spec)
            if form.is_valid():
                form.save()
            seen_ids.add(form.instance.pk)

@permission_required('main.change_package')
@never_cache
def signoff_options(request, name, repo, arch):
    packages = get_list_or_404(Package, pkgbase=name,
            arch__name=arch, repo__name__iexact=repo, repo__testing=True)
    package = packages[0]

    if request.user != package.packager and \
            request.user not in package.maintainers:
        return render(request, '403.html', status=403)

    try:
        spec = SignoffSpecification.objects.get_from_package(package)
    except SignoffSpecification.DoesNotExist:
        # create a fake one, but don't save it just yet
        spec = SignoffSpecification(pkgbase=package.pkgbase,
                pkgver=package.pkgver, pkgrel=package.pkgrel,
                epoch=package.epoch, arch=package.arch, repo=package.repo)

    if spec.user is None:
        spec.user = request.user

    if request.POST:
        form = SignoffOptionsForm(request.POST, instance=spec)
        if form.is_valid():
            if form.cleaned_data['apply_all']:
                _signoff_options_all(request, name, repo)
            else:
                form.save()
            return redirect('package-signoffs')
    else:
        form = SignoffOptionsForm(instance=spec)

    context = {
        'packages': packages,
        'package': package,
        'form': form,
    }
    return render(request, 'packages/signoff_options.html', context)

class SignoffJSONEncoder(DjangoJSONEncoder):
    '''Base JSONEncoder extended to handle all serialization of all classes
    related to signoffs.'''
    signoff_group_attrs = ['arch', 'last_update', 'maintainers', 'packager',
            'pkgbase', 'repo', 'signoffs', 'target_repo', 'version']
    signoff_spec_attrs = ['required', 'enabled', 'known_bad', 'comments']
    signoff_attrs = ['user', 'created', 'revoked']

    def default(self, obj):
        if isinstance(obj, PackageSignoffGroup):
            data = {attr: getattr(obj, attr)
                    for attr in self.signoff_group_attrs}
            data['pkgnames'] = [p.pkgname for p in obj.packages]
            data['package_count'] = len(obj.packages)
            data['approved'] = obj.approved()
            data.update((attr, getattr(obj.specification, attr))
                    for attr in self.signoff_spec_attrs)
            return data
        elif isinstance(obj, Signoff):
            return {attr: getattr(obj, attr) for attr in self.signoff_attrs}
        elif isinstance(obj, Arch) or isinstance(obj, Repo):
            return unicode(obj)
        elif isinstance(obj, User):
            return obj.username
        elif isinstance(obj, set):
            return list(obj)
        return super(SignoffJSONEncoder, self).default(obj)

@permission_required('main.change_package')
def signoffs_json(request):
    signoff_groups = sorted(get_signoff_groups(), key=attrgetter('pkgbase'))
    data = {
        'version': 2,
        'signoff_groups': signoff_groups,
    }
    to_json = json.dumps(data, ensure_ascii=False, cls=SignoffJSONEncoder)
    response = HttpResponse(to_json, content_type='application/json')
    return response

# vim: set ts=4 sw=4 et: