# This file is part of k4ever, a point-of-sale system
#   Contact............  <k4ever@lists.someserver.de>
#   Website............  http://k4ever.someserver.de/
#   Bug tracker........  http://k4ever.someserver.de/report
#
# Licensed under GNU Affero General Public License v3 or later

from easy_thumbnails.files import get_thumbnailer
from piston.handler import BaseHandler
from piston.utils import rc
from k4ever.buyable.models import *
from k4ever.transaction.models import *
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import Group
from django.core.exceptions import MultipleObjectsReturned
from decorators import *
from main.helper import getUserFromAuthblob
from collections import Iterable
from decimal import Decimal, InvalidOperation
from helper import *
from django.db.models import Q
import datetime

# Available image sizes for thumbnails
THUMB_SIZES = [(48,48), (64,64), (100,100), (150,150), (220, 220), (330, 330), (470, 470), (680, 680)]

class BuyableItemHandler(BaseHandler):
	"""Handler responsible for getting and buying items."""
	
	allowed_methods = ('GET', 'POST')
	#fields = ('id', 'description')
	model = Buyable
	exclude = ('_*',)

	BUY_ITEM, BUY_DEPOSIT, BUY_ITEM_AND_DEPOSIT = range(3)
	
	def read(self, request, itemId=None, bulkBuy=False):
		"""Get one or multiple items.
		
		 - type: Only get items belonging to this type
		 - barcode: Return only item(s) with this barcode
		
		Note that neither type nor barcode is used if an item id
		is specified.
		"""
		
		if bulkBuy:
			return getError(rc.NOT_IMPLEMENTED, "Bulk buying does not support GET")
		
		barcode = request.GET.get('barcode', None)
		if itemId == None:
			obj = Buyable.objects.all()
			if barcode and barcode != '':
				# try to get specific object or return 404
				try:
					return Buyable.objects.get(barcode=barcode)
				except MultipleObjectsReturned:
					return getError(rc.DUPLICATE_ENTRY, "We found more than one entry with this barcode. Bad.")
				except Buyable.DoesNotExist:
					return rc.NOT_FOUND
			else:
				if request.GET.has_key('type'):
					obj = Buyable.objects.filter(buyableType__name=request.GET['type'])
				return obj
		try:
			return Buyable.objects.get(id=itemId)
		except Buyable.DoesNotExist:
			return getError(rc.NOT_FOUND, "This buyable does not exist in our database")
	
	@manglePluginPerms
	def create(self, request, itemId=None, bulkBuy=False):
		"""Buy one or multiple :class:`Buyable <buyable.models.Buyable>` items.
		"""
		
		if not request.content_type:
			request.data = request.POST
		
		if bulkBuy:
			return self.bulkBuy(request)
		else:
			return self.buyItem(request, itemId)
	
	def buyItem(self, request, itemId):
		"""Buy a :class:`Buyable <buyable.models.Buyable>` item.
		
		- deposit:		Set to 0 for no deposit, 1 for deposit and 2 for item+deposit (default 0)
		- amount:		amount of items to buy (default 1)
		"""
		if not itemId:
			return rc.BAD_REQUEST
		item = None
		try:
			item = Buyable.objects.get(id=itemId)
		except Buyable.DoesNotExist:
			return getError(rc.NOT_FOUND, "A Buyable with that id does not exist.")
		
		# parse post data 
		deposit = getInt(request.data, 'deposit', self.BUY_ITEM)
		amount = getInt(request.data, 'amount', 1)
		if amount < 1:
			return getError(rc.BAD_REQUEST, "Buying zero or negative amounts is not supported.")
		if amount > 30:
			return getError(rc.BAD_REQUEST, "You are trying to buy more than 30 items at once. This is not permitted. If you think it should, mail the admins / fix this in the handlers.py")
		if (not item.hasDeposit() and deposit != self.BUY_ITEM) or \
				deposit not in (self.BUY_ITEM, self.BUY_DEPOSIT, self.BUY_ITEM_AND_DEPOSIT):
			return getError(rc.BAD_REQUEST, "Either this item has no deposit or you specified an invalid deposit buy mode.")
		order = Order(user=request.user)
		order.save()
		
		for i in range(amount):
			if deposit == self.BUY_ITEM or deposit == self.BUY_ITEM_AND_DEPOSIT:
				p = Purchase(order=order, buyable=item, isDeposit=False)
				p.save(saveOrder=False)
			if deposit == self.BUY_DEPOSIT or deposit == self.BUY_ITEM_AND_DEPOSIT:
				p = Purchase(order=order, buyable=item, isDeposit=True)
				p.save(saveOrder=False)
		order.save()
		
		return {'success': True, 'balance': request.user.get_profile().balance}
		
	def bulkBuy(self, request):
		"""Buy a :class:`Buyable <buyable.models.Buyable>` item.
		
		To buy multiple items, the body of the POST-request has to
		be either JSON or YAML. 
		- items:    List of items to buy.
		- deposits: List of items to buy deposit for.
		"""
		if not request.content_type:
			return getError(rc.BAD_REQUEST, "The content-type of the request must not be empty/urlencoded")
		
		if not request.data.has_key("items") and not request.data.has_key("deposits"):
			return getError(rc.BAD_REQUEST, "You need to specify either items or deposits (or both).")
		
		if not request.data.has_key("items"):
			request.data['items'] = []
		if not request.data.has_key("deposits"):
			request.data['deposits'] = []
		
		itemList = []
		try:
			if not isinstance(request.data['items'], Iterable):
				raise TypeError()
			itemList += request.data['items']
			if request.data.has_key('items'):
				if not isinstance(request.data['deposits'], Iterable):
					raise TypeError()
			itemList += request.data['deposits']
		except TypeError:
			return getError(rc.BAD_REQUEST, "The items/deposists parameter have to be a list.")
		
		if len(itemList) > 30:
			return getError(rc.BAD_REQUEST, "You are trying to buy more than 30 items at once. This is not permitted. If you think it should, mail the admins / fix this in the handlers.py")
		
		if len(itemList) == 0:
			return getError(rc.BAD_REQUEST, "Your request contains no items/deposits.")
		
		ids = {}
		for item in itemList:
			if not ids.has_key(item):
				try:
					ids[item] = Buyable.objects.get(id=item)
				except Buyable.DoesNotExist:
					return getError(rc.NOT_FOUND, "The item with the id '%s' could not be found" % (item,))
				except ValueError:
					return getError(rc.NOT_FOUND, "Item ids should be numeric (and preferably integers)")
				if item in request.data['deposits'] and not ids[item].hasDeposit():
					return getError(rc.BAD_REQUEST, "Item '%s' cant be bought with deposit" % (item,))
		
		order = Order(user=request.user)
		order.save() # one save, for the id
		# buy items
		for item in request.data['items']:
			p = Purchase(order=order, buyable=ids[item], isDeposit=False)
			p.save(saveOrder=False)
		# buy deposits
		for item in request.data['deposits']:
			p = Purchase(order=order, buyable=ids[item], isDeposit=True)
			p.save(saveOrder=False)
		order.save()
		
		return {'success': True, 'balance': request.user.get_profile().balance}


class BuyableTypeHandler(BaseHandler):
	"""Handler for listing all :class:`BuyableType <buyable.models.BuyableType>`.

	This class only supports read requests which won't accept
	any arguments. It will give you a list of buyable-types.
	Nothing more, nothing less.
	"""
	allowed_methods = ('GET',)
	model = BuyableType

class HistoryHandler(BaseHandler):
	"""Handler providing access to the user's history """
	allowed_methods = ('GET',)
	fields = ('id', 'price', 'dateTime', ('purchase_set', (('buyable', ('id', 'name')), 'price', 'isDeposit')))
	
	@manglePluginPerms
	def read(self, request):
		"""Get the user's history
		
		 - num: Number of entries to return
		"""
		num = getInt(request.GET, 'num', 0)
		qset = Order.objects.filter(user=request.user).order_by("-dateTime")
		if num > 0:
			return qset[:num]
		return qset

class ImgSizesHandler(BaseHandler):
	""" Handler for listing all available tumbnailsizes """
	allowed_methods = ('GET',)
	
	def read(self, request):
		return THUMB_SIZES

class ImgThumbHandler(BaseHandler):
	""" Handler providing access to thumbnails for images of buyables """
	allowed_methods = ('GET',)
	
	def read(self, request, itemId, xSize, ySize):
		thumbSize = None
		try:
			thumbSize = (int(xSize), int(ySize))
		except ValueError:
			return getError(rc.BAD_REQUEST, "Something is seriously broken, django urls SHOULD have parsed out the non-number thingie.")

		if thumbSize not in THUMB_SIZES:
			return getError(rc.BAD_REQUEST, "The requested thumbnailsize is not available.")

		item = None
		try:
			item = Buyable.objects.get(id=itemId)
		except Buyable.DoesNotExist:
			return getError(rc.NOT_FOUND, "The item with the id '%s' could not be found" % (itemId,))
		
		thumbnail_options = dict(size=thumbSize)
		thumb = get_thumbnailer(item.image).get_thumbnail(thumbnail_options)
		return thumb._get_url()


class TransactionTransactHandler(BaseHandler):
	"""Handler for transaction.

	This handler takes care of adding money to accounts and returning
	previous money transfers
	"""

	allowed_methods = ('GET', 'POST')
	model = Transaction
	fields = ('amount', 'dateTime', 'checked', ('transactionType', ('id', 'name')))
	
	@manglePluginPerms
	def read(self, request):
		"""Return the user's last transactions

		 - num: Number of entries to return
		 """
		num = getInt(request.GET, 'num', 0)
		if num < 0:
			return getError(rc.BAD_REQUEST, "Please supply a positive number of entries.")
		
		userTrans  = Transaction.objects.filter(user=request.user).order_by("-dateTime")
		if num > 0:
			return userTrans[:num]
		return userTrans
	
	@manglePluginPerms
	def create(self, request):
		"""Transact money to an account

		 - amount: 	[req] Amount to add to the user's account (!= 0)
		 - type:   	[req] Type of transaction (id)
		"""
		
		if not request.content_type:
			request.data = request.POST
		
		amount = getDecimal(request.data, 'amount', Decimal(0))
		tTypeId  = getInt(request.data, 'type', -1)
		
		if amount == Decimal("0.0"):
			return getError(rc.BAD_REQUEST, "An amount equaling zero is not supported")
		tType = None
		try:
			tType = TransactionType.objects.get(id=tTypeId)
		except TransactionType.DoesNotExist:
			return getError(rc.BAD_REQUEST, "Your TransactionType could not be found")
		trans = Transaction(user=request.user, transactionType=tType, amount=amount, checked=not tType.needsCheck)
		trans.save()
		return {'success': True, 'balance': request.user.get_profile().balance}

class TransactionTypeHandler(BaseHandler):
	"""Handler for :class:`Transaction Types <transaction.models.TransactionType>`
	
	Supplies a list of Transaction Types
	"""
	allowed_methods = ('GET',)
	model = TransactionType

class TransactionVirtualHandler(BaseHandler):
	""" Handler for :class:`Virtual Transaction <transaction.models.VirtualTransaction>`
	Allows to make transactions between users. """
	allowed_methods = ('GET', 'POST')
	model = VirtualTransaction
	fields = ('id', 'amount', 'dateTime', 'comment', ('user', ('id', 'username',)), ('recipient', ('id', 'username',)))
	
	@manglePluginPerms
	def read(self, request):
		"""Return the user's last virtual transactions (inbound and outbound)

		 - num: Number of entries to return
		 """
		num = getInt(request.GET, 'num', 0)
		if num < 0:
			return getError(rc.BAD_REQUEST, "Please supply a positive number of entries.")
		
		userTrans  = VirtualTransaction.objects.filter(Q(user=request.user) | Q(recipient=request.user)).order_by("-dateTime")
		if num > 0:
			return userTrans[:num]
		return userTrans
	
	@manglePluginPerms
	def create(self, request):
		""" Transact money from the users to another users account.

		 - amount: 		[req] Amount to transact
		 - recipient:	[req] User that will get the money
		 - comment:		[req] Comment, why the money was transacted
		"""
		
		amount = getDecimal(request.data, 'amount', Decimal(0))
		
		if amount < Decimal("0.01"):
			return getError(rc.BAD_REQUEST, "You can't transact negatives amount to another users account.")
		
		comment = request.data.get('comment', None)
		if not comment:
			return getError(rc.BAD_REQUEST, "Please supply a comment for the transaction")

		recipientStr = request.data.get('recipient', None)
		recipient = None
		try:
			recipient = User.objects.get(username=recipientStr)
		except User.DoesNotExist:
			return getError(rc.BAD_REQUEST, "The recipient user does not exist.")
		trans = VirtualTransaction(user=request.user, recipient=recipient, amount=amount, comment=comment)
		trans.save()
		return {'success': True, 'balance': request.user.get_profile().balance}

class AccountBalanceHandler(BaseHandler):
	"""Handler for the user's account balance"""
	allowed_methods = ('GET',)
	
	@manglePluginPerms
	def read(self, request):
		"""Returns the user's current account balance"""
		balance = request.user.get_profile().balance
		return {'balance': balance}

class AuthBlobHandler(BaseHandler):
	"""Handler to read and write a user's authblob

	Currently these functions are only available for a plugin user.
	Other users will get a rc.FORBIDDEN. Keep in mind that to use
	these functions, a plugin needs the permissions to do this in its
	configuration.
	"""
	allowed_methods = ('GET', 'POST')
	
	@requirePlugin
	@manglePluginPerms
	def read(self, request):
		"""Read the user's authblob
		
		To use this function the plugin needs 
		:attr:`main.models.Plugin.pluginCanReadAuthblob` to be true.
		"""
		if not request.plugin.pluginCanReadAuthblob:
			return getError(rc.FORBIDDEN, "This plugin is not allowed to read the user's authblob")
		return {'authblob': request.pluginperms.authblob}
	
	@requirePlugin
	@manglePluginPerms
	def create(self, request):
		"""Write the user's authblob.

		To use this function the plugin needs
		:attr:`main.models.Plugin.pluginCanWriteAuthblob` to be true.

		.. Note:: In fact this method *should* be an update method, but for 
					compability reasons (wget usage) it was decided to make
					this accessible as a create (POST) hook.

		"""
		if not request.plugin.pluginCanWriteAuthblob:
			return getError(rc.FORBIDDEN, "This plugin is not allowed to write the user's authblob")
		if not request.data.has_key('authblob'):
			return getError(rc.BAD_REQUEST, "To change the user's auth blob you actually need to provide one")
		request.pluginperms.authblob = request.data['authblob']
		request.pluginperms.authblob.save()
		
		return {'success': True}

class AuthUserHandler(BaseHandler):
	""" Handler for mapping an authblob to a user

	This handler is only available to plugins and only if
	:attr:`unique authblob <main.models.Plugin.uniqueAuthblob>`
	is set for this plugin. Then it will provide a mapping from
	an authblob to a specific user.
	"""
	allowed_methods = ('GET')
	fields = ('id', 'username')
	
	@requirePlugin
	def read(self, request):
		"""Returns an user if one can be found, else rc.NOT_FOUND

		 - authblob: [required] Authblob to search
		"""
		if not request.plugin.uniqueAuthblob:
			return getError(rc.BAD_REQUEST, "This plugin does not support unique auth blobs, therefore we can't identify a user uniquely by their authblob")

		if not request.GET.has_key('authblob') or request.GET['authblob'] == '':
			return getError(rc.BAD_REQUEST, "Authblob was empty.")
		
		user = getUserFromAuthblob(request.GET['authblob'], request.plugin)
		if user:
			return user
		return getError(rc.NOT_FOUND, "A user with that authblob could not be found.")

class ConfigHandler(BaseHandler):
	""" Handler for API configuration values

	This handler provides some API related configuration values"""
	allowed_methods = ('GET',)
	
	def read(self, request):
		"""Get API configuration values

		Currently returns the API version and mediaurl (where to
		find images etc.)
		"""
		return {
					'version':	'0.1',
					'mediaurl':	'http://devcat.someserver.de:13805/media',
			   }

