# -*- coding: utf-8 -*-

from django.urls import reverse
from django.conf.urls import url
from django.utils.translation import ugettext as _

from rest_framework.exceptions import ValidationError
from rest_framework import permissions
from rest_framework.response import Response
from rest_framework.views import APIView

from stock.models import StockItem
from stock.serializers import StockItemSerializer

from barcode.barcode import load_barcode_plugins, hash_barcode


class BarcodeScan(APIView):
    """
    Endpoint for handling generic barcode scan requests.

    Barcode data are decoded by the client application,
    and sent to this endpoint (as a JSON object) for validation.

    A barcode could follow the internal InvenTree barcode format,
    or it could match to a third-party barcode format (e.g. Digikey).

    When a barcode is sent to the server, the following parameters must be provided:

    - barcode: The raw barcode data

    plugins:
    Third-party barcode formats may be supported using 'plugins'
    (more information to follow)

    hashing:
    Barcode hashes are calculated using MD5

    """

    permission_classes = [
        permissions.IsAuthenticated,
    ]

    def post(self, request, *args, **kwargs):
        """
        Respond to a barcode POST request
        """

        data = request.data

        if 'barcode' not in data:
            raise ValidationError({'barcode': _('Must provide barcode_data parameter')})

        plugins = load_barcode_plugins()

        barcode_data = data.get('barcode')

        # Look for a barcode plugin which knows how to deal with this barcode
        plugin = None

        for plugin_class in plugins:
            plugin_instance = plugin_class(barcode_data)

            if plugin_instance.validate():
                plugin = plugin_instance
                break

        match_found = False
        response = {}

        response['barcode_data'] = barcode_data

        # A plugin has been found!
        if plugin is not None:
            
            # Try to associate with a stock item
            item = plugin.getStockItem()

            if item is None:
                item = plugin.getStockItemByHash()

            if item is not None:
                response['stockitem'] = plugin.renderStockItem(item)
                response['url'] = reverse('stock-item-detail', kwargs={'pk': item.id})
                match_found = True

            # Try to associate with a stock location
            loc = plugin.getStockLocation()

            if loc is not None:
                response['stocklocation'] = plugin.renderStockLocation(loc)
                response['url'] = reverse('location-detail', kwargs={'pk': loc.id})
                match_found = True

            # Try to associate with a part
            part = plugin.getPart()

            if part is not None:
                response['part'] = plugin.renderPart(part)
                response['url'] = reverse('part-detail', kwargs={'pk': part.id})
                match_found = True

            response['hash'] = plugin.hash()
            response['plugin'] = plugin.name

        # No plugin is found!
        # However, the hash of the barcode may still be associated with a StockItem!
        else:
            hash = hash_barcode(barcode_data)

            response['hash'] = hash
            response['plugin'] = None

            # Try to look for a matching StockItem
            try:
                item = StockItem.objects.get(uid=hash)
                serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
                response['stockitem'] = serializer.data
                response['url'] = reverse('stock-item-detail', kwargs={'pk': item.id})
                match_found = True
            except StockItem.DoesNotExist:
                pass

        if not match_found:
            response['error'] = _('No match found for barcode data')
        else:
            response['success'] = _('Match found for barcode data')

        return Response(response)


class BarcodeAssign(APIView):
    """
    Endpoint for assigning a barcode to a stock item.
    
    - This only works if the barcode is not already associated with an object in the database
    - If the barcode does not match an object, then the barcode hash is assigned to the StockItem
    """

    permission_classes = [
        permissions.IsAuthenticated
    ]

    def post(self, request, *args, **kwargs):

        data = request.data

        if 'barcode' not in data:
            raise ValidationError({'barcode': _('Must provide barcode_data parameter')})

        if 'stockitem' not in data:
            raise ValidationError({'stockitem': _('Must provide stockitem parameter')})

        barcode_data = data['barcode']

        try:
            item = StockItem.objects.get(pk=data['stockitem'])
        except (ValueError, StockItem.DoesNotExist):
            raise ValidationError({'stockitem': _('No matching stock item found')})

        plugins = load_barcode_plugins()

        plugin = None

        for plugin_class in plugins:
            plugin_instance = plugin_class(barcode_data)

            if plugin_instance.validate():
                plugin = plugin_instance
                break

        match_found = False

        response = {}

        response['barcode_data'] = barcode_data

        # Matching plugin was found
        if plugin is not None:
            
            hash = plugin.hash()
            response['hash'] = hash
            response['plugin'] = plugin.name

            # Ensure that the barcode does not already match a database entry

            if plugin.getStockItem() is not None:
                match_found = True
                response['error'] = _('Barcode already matches StockItem object')

            if plugin.getStockLocation() is not None:
                match_found = True
                response['error'] = _('Barcode already matches StockLocation object')

            if plugin.getPart() is not None:
                match_found = True
                response['error'] = _('Barcode already matches Part object')

            if not match_found:
                item = plugin.getStockItemByHash()

                if item is not None:
                    response['error'] = _('Barcode hash already matches StockItem object')
                    match_found = True

        else:
            hash = hash_barcode(barcode_data)

            response['hash'] = hash
            response['plugin'] = None

            # Lookup stock item by hash
            try:
                item = StockItem.objects.get(uid=hash)
                response['error'] = _('Barcode hash already matches StockItem object')
                match_found = True
            except StockItem.DoesNotExist:
                pass

        if not match_found:
            response['success'] = _('Barcode associated with StockItem')

            # Save the barcode hash
            item.uid = response['hash']
            item.save()

            serializer = StockItemSerializer(item, part_detail=True, location_detail=True, supplier_part_detail=True)
            response['stockitem'] = serializer.data

        return Response(response)


barcode_api_urls = [

    url(r'^link/$', BarcodeAssign.as_view(), name='api-barcode-link'),
    
    # Catch-all performs barcode 'scan'
    url(r'^.*$', BarcodeScan.as_view(), name='api-barcode-scan'),
]
