from datetime import date, timedelta

from django.conf import settings
from django.contrib import messages
from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse_lazy
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404
from django.utils.translation import ugettext as _
from django.views.generic import (CreateView, DeleteView, DetailView, FormView,
                                  TemplateView, UpdateView, View)

from ideascube.views import CSVExportMixin
from ideascube.decorators import staff_member_required

from .forms import (EntryForm, ExportEntryForm, ExportLoanForm,
                    InventorySpecimenForm, LoanForm, ReturnForm, SpecimenForm,
                    StockImportForm)
from .models import (Entry, Inventory, InventorySpecimen, Loan, Specimen,
                     StockItem)

user_model = get_user_model()


class EntryView(FormView):
    fields = '__all__'
    template_name = 'monitoring/entry.html'
    form_class = EntryForm
    success_url = reverse_lazy('monitoring:entry')

    def form_valid(self, form):
        count = 0
        module = form.cleaned_data['module']
        activity = form.cleaned_data['activity']
        partner = form.cleaned_data['partner']
        activity_select = form.cleaned_data.get('activity_list')
        if not activity and activity_select:
            activity = activity_select
        for serial in form.cleaned_data['serials']:
            try:
                user = user_model.objects.get(serial=serial)
            except user_model.DoesNotExist:
                msg = _('No user found with serial {serial}')
                msg = msg.format(serial=serial)
                messages.add_message(self.request, messages.ERROR, msg)
            else:
                Entry.objects.create(user=user, module=module,
                                     activity=activity, partner=partner)
                count += 1
        if count:
            msg = _('Created {count} entries')
            msg = msg.format(count=count)
            messages.add_message(self.request, messages.SUCCESS, msg)
        else:
            msg = _('No entry created.')
            messages.add_message(self.request, messages.WARNING, msg)
        return super().form_valid(form)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['entries'] = Entry.objects.all()[:50]
        context['export_form'] = ExportEntryForm()
        context['MONITORING_ENTRY_EXPORT_FIELDS'] = settings.MONITORING_ENTRY_EXPORT_FIELDS  # noqa
        return context

entry = staff_member_required(EntryView.as_view())


class ExportEntry(CSVExportMixin, View):
    prefix = 'entry'

    def get(self, *args, **kwargs):
        self.form = ExportEntryForm(self.request.GET)
        if self.form.is_valid():
            return super().get(*args, **kwargs)
        else:
            msg = _('Error while processing entries export')
            messages.add_message(self.request, messages.ERROR, msg)
            messages.add_message()
            return HttpResponseRedirect(reverse_lazy('monitoring:entry'))

    def get_headers(self):
        self.fields = ['module', 'date', 'activity', 'partner']
        self.fields.extend(settings.MONITORING_ENTRY_EXPORT_FIELDS)
        return self.fields

    def get_items(self):
        qs = Entry.objects.order_by('created_at')
        if self.form.cleaned_data['since']:
            qs = qs.filter(created_at__gte=self.form.cleaned_data['since'])
        return qs

    def get_row(self, entry):
        row = {'module': entry.module, 'date': entry.created_at,
               'activity': entry.activity, 'partner': entry.partner}
        for field in settings.MONITORING_ENTRY_EXPORT_FIELDS:
            row[field] = getattr(entry.user, field, None)
        return row

export_entry = staff_member_required(ExportEntry.as_view())


class StockListMixin(object):

    def get_stock(self):
        stock = []
        for key, name in StockItem.MODULES:
            stock.append({
                'key': key,
                'name': name,
                'objects': StockItem.objects.filter(module=key, physical=True)
            })
        return stock


class Stock(StockListMixin, TemplateView):
    template_name = "monitoring/stock.html"
    model = StockItem

    def get_context_data(self, **kwargs):
        return {
            'stock': self.get_stock(),
            'inventory_list': Inventory.objects.all()
        }
stock = staff_member_required(Stock.as_view())


class InventoryDetail(StockListMixin, DetailView):
    model = Inventory
    template_name = 'monitoring/inventory.html'

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['stock'] = self.get_stock()
        context['inventoryspecimen_form'] = InventorySpecimenForm(
            initial={'inventory': self.object})
        return context

inventory = staff_member_required(InventoryDetail.as_view())


class InventoryUpdate(UpdateView):
    model = Inventory
    fields = '__all__'
inventory_update = staff_member_required(InventoryUpdate.as_view())


class InventoryCreate(CreateView):
    model = Inventory
    fields = '__all__'
inventory_create = staff_member_required(InventoryCreate.as_view())


class InventoryDelete(DeleteView):
    model = Inventory
    success_url = reverse_lazy('monitoring:stock')
inventory_delete = staff_member_required(InventoryDelete.as_view())


class InventoryExport(CSVExportMixin, DetailView):
    model = Inventory

    def get(self, *args, **kwargs):
        self.object = self.get_object()
        return super().get(*args, **kwargs)

    def get_headers(self):
        self.headers = ['module', 'name', 'description', 'barcode', 'serial',
                        'count', 'comments', 'status']
        return self.headers

    def get_items(self):
        return Specimen.objects.physical()

    def get_row(self, specimen):
        return {
            'module': specimen.item.module,
            'name': specimen.item.name,
            'description': specimen.item.description,
            'barcode': specimen.barcode,
            'serial': specimen.serial,
            'count': specimen.count,
            'comments': specimen.comments,
            'status': specimen.count if specimen in self.object else 'ko'
        }

    def get_filename(self):
        filename = "_".join([
            'inventory',
            settings.IDEASCUBE_ID,
            str(self.object.made_at)
        ])
        return filename
inventory_export = staff_member_required(InventoryExport.as_view())


class StockItemUpdate(UpdateView):
    model = StockItem
    fields = '__all__'
stockitem_update = staff_member_required(StockItemUpdate.as_view())


class StockItemCreate(CreateView):
    model = StockItem
    fields = '__all__'

    def get_initial(self):
        initial = super().get_initial()
        initial['module'] = self.request.GET.get('module')
        return initial

stockitem_create = staff_member_required(StockItemCreate.as_view())


class StockItemDelete(DeleteView):
    model = StockItem
    success_url = reverse_lazy('monitoring:stock')
stockitem_delete = staff_member_required(StockItemDelete.as_view())


class StockExport(CSVExportMixin, View):
    model = Specimen
    prefix = 'stock'

    def get_headers(self):
        return ['module', 'name', 'description', 'serial', 'barcode', 'count']

    def get_items(self):
        qs = super().get_items()
        qs = qs.select_related()  # Eagerly JOIN with the StockItem table
        qs = qs.filter(item__book__isnull=True)

        return qs

    def get_row(self, specimen):
        return {
            'module': specimen.item.module, 'name': specimen.item.name,
            'description': specimen.item.description,
            'serial': specimen.serial or '', 'barcode': specimen.barcode or '',
            'count': specimen.count,
        }
stock_export = staff_member_required(StockExport.as_view())


class StockImport(FormView):
    form_class = StockImportForm
    template_name = 'monitoring/stock_import.html'
    success_url = reverse_lazy('monitoring:stock')

    def form_valid(self, form):
        items, errors = form.save()

        if items:
            messages.success(
                self.request,
                _('Successfully imported {} items').format(len(items)))

        for error in errors:
            messages.error(self.request, error)

        return super().form_valid(form)
stock_import = staff_member_required(StockImport.as_view())


class SpecimenUpdate(UpdateView):
    model = Specimen
    form_class = SpecimenForm
specimen_update = staff_member_required(SpecimenUpdate.as_view())


class SpecimenCreate(CreateView):
    model = Specimen
    form_class = SpecimenForm

    def get_initial(self):
        item = get_object_or_404(StockItem, pk=self.kwargs['item_pk'])
        return {'item': item}
specimen_create = staff_member_required(SpecimenCreate.as_view())


class SpecimenDelete(DeleteView):
    model = Specimen
    success_url = reverse_lazy('monitoring:stock')
specimen_delete = staff_member_required(SpecimenDelete.as_view())


class InventorySpecimenAdd(View):

    def get(self, request, *args, **kwargs):
        inventory = get_object_or_404(Inventory,
                                      pk=self.kwargs['inventory_pk'])
        specimen = get_object_or_404(Specimen, pk=self.kwargs['specimen_pk'])
        InventorySpecimen.objects.get_or_create(
            inventory=inventory, specimen=specimen,
            defaults={'count': specimen.count})
        url = reverse_lazy('monitoring:inventory',
                           kwargs={'pk': self.kwargs['inventory_pk']})
        return HttpResponseRedirect(url)
inventoryspecimen_add = staff_member_required(
    InventorySpecimenAdd.as_view())


class InventorySpecimenByBarCode(FormView):
    form_class = InventorySpecimenForm
    fields = '__all__'

    def redirect(self, form):
        url = reverse_lazy('monitoring:inventory',
                           kwargs={'pk': form.cleaned_data['inventory'].pk})
        return HttpResponseRedirect(url)

    def form_valid(self, form):
        form.save()
        msg = _('Specimen {specimen} has been added to {inventory} inventory')
        msg = msg.format(specimen=form.instance.specimen.barcode,
                         inventory=form.instance.inventory.made_at)
        messages.add_message(self.request, messages.SUCCESS, msg)
        return self.redirect(form)

    def form_invalid(self, form):
        msg = _('Specimen {specimen} not found or already done.')
        msg = msg.format(specimen=form.data['specimen'])
        messages.add_message(self.request, messages.ERROR, msg)
        return self.redirect(form)


inventoryspecimen_bybarcode = staff_member_required(
    InventorySpecimenByBarCode.as_view())


class InventorySpecimenRemove(View):

    def get(self, request, *args, **kwargs):
        inventory = get_object_or_404(Inventory,
                                      pk=self.kwargs['inventory_pk'])
        specimen = get_object_or_404(Specimen, pk=self.kwargs['specimen_pk'])
        InventorySpecimen.objects.filter(inventory=inventory,
                                         specimen=specimen).delete()
        url = reverse_lazy('monitoring:inventory',
                           kwargs={'pk': self.kwargs['inventory_pk']})
        return HttpResponseRedirect(url)
inventoryspecimen_remove = staff_member_required(
    InventorySpecimenRemove.as_view())


class InventorySpecimenIncrease(View):

    def get(self, request, *args, **kwargs):
        obj = get_object_or_404(InventorySpecimen, pk=self.kwargs['pk'])
        obj.count = obj.count + 1
        obj.save()
        url = reverse_lazy('monitoring:inventory',
                           kwargs={'pk': obj.inventory.pk})
        return HttpResponseRedirect(url)
inventoryspecimen_increase = staff_member_required(
    InventorySpecimenIncrease.as_view())


class InventorySpecimenDecrease(View):

    def get(self, request, *args, **kwargs):
        obj = get_object_or_404(InventorySpecimen, pk=self.kwargs['pk'])
        obj.count = max(obj.count - 1, 0)
        obj.save()
        url = reverse_lazy('monitoring:inventory',
                           kwargs={'pk': obj.inventory.pk})
        return HttpResponseRedirect(url)
inventoryspecimen_decrease = staff_member_required(
    InventorySpecimenDecrease.as_view())


class ItemLoan(TemplateView):
    template_name = 'monitoring/loan.html'

    def get_context_data(self, **kwargs):
        due_date = date.today() + timedelta(days=settings.LOAN_DURATION)
        defaults = {
            'loan_form': LoanForm(initial={'due_date': due_date}),
            'return_form': ReturnForm,
            'loans': Loan.objects.due(),
            'export_form': ExportLoanForm
        }
        defaults.update(kwargs)
        return super().get_context_data(**defaults)

    def post(self, request, *args, **kwargs):
        context = {}
        if 'do_loan' in request.POST:
            loan_form = LoanForm(data=request.POST)
            if loan_form.is_valid():
                specimen = loan_form.cleaned_data['specimen']
                user = loan_form.cleaned_data['user']
                Loan.objects.create(
                    specimen=specimen,
                    user=user,
                    comments=loan_form.cleaned_data['comments'],
                    due_date=loan_form.cleaned_data['due_date'],
                    by=request.user)
                msg = _('Item {item} has been loaned to {user}')
                msg = msg.format(item=specimen.item, user=user)
                messages.add_message(self.request, messages.SUCCESS, msg)
            else:
                context['loan_form'] = loan_form
        elif 'do_return' in request.POST:
            return_form = ReturnForm(data=request.POST)
            if return_form.is_valid():
                loan = return_form.cleaned_data['loan']
                if loan:
                    loan.mark_returned()
                    msg = _('Item {item} has been returned')
                    msg = msg.format(item=loan.specimen.item)
                    status = messages.SUCCESS
                else:
                    msg = _('Item not found')
                    status = messages.ERROR
                messages.add_message(self.request, status, msg)
            else:
                context['return_form'] = return_form
        return self.render_to_response(self.get_context_data(**context))
loan = staff_member_required(ItemLoan.as_view())


class ExportLoan(CSVExportMixin, View):
    prefix = 'loan'

    def get(self, *args, **kwargs):
        self.form = ExportLoanForm(self.request.GET)
        if self.form.is_valid():
            return super().get(*args, **kwargs)
        else:
            msg = _('Error while processing loans export')
            messages.add_message(self.request, messages.ERROR, msg)
            messages.add_message()
            return HttpResponseRedirect(reverse_lazy('monitoring:loan'))

    def get_headers(self):
        self.fields = ['item', 'barcode', 'serial', 'user', 'loaned at',
                       'due date', 'returned at', 'comments']
        return self.fields

    def get_items(self):
        qs = Loan.objects.order_by('created_at')
        if self.form.cleaned_data['since']:
            qs = qs.filter(created_at__gte=self.form.cleaned_data['since'])
        return qs

    def get_row(self, entry):
        return {
            'item': str(entry.specimen.item),
            'barcode': entry.specimen.barcode,
            'serial': entry.specimen.serial,
            'user': entry.user.serial,
            'loaned at': entry.created_at,
            'due date': entry.due_date,
            'returned at': entry.returned_at,
            'comments': entry.comments
        }

export_loan = staff_member_required(ExportLoan.as_view())