import urllib.request
import urllib.parse
import urllib.error

import bcrypt
from io import BytesIO
from Campus.Entity import Campus

from User.Business.UserManager import UserManager
from User.Business.UserRoleManager import UserRoleManager
from User.Entity.UserRole import UserRole
from lawrence.base import Handler
from lawrence.HTTPStatus import *
from User.Entity.User import User
from lawrence.face_detector import DetectFace
from lawrence.util import GroupBy, Grouping

class Self (Handler):
	__method__ = 'GET'
	__path__ = '/User/self/'

	def Handle(self):
		self.RequireLogin()

		raise SeeOther('/user/%s/' % self.CurrentUser.Id)

class Index (Handler):
	__method__ = 'GET'
	__path__ = '/[Uu]ser/'

	def Handle(self, *,
			groupBy: str = None):
		self.RequirePermission('/user/', 'read')

		if groupBy == 'Role':
			self.Model.GroupBy = 'Role'
			self.Model.RoleList = GroupBy(self.DatabaseCursor.Execute('''
				SELECT
					Role.Name AS RoleName,
					User.Id AS Id,
					User.FullName AS FullName
				FROM
					UserRoleMap
					INNER JOIN User
					  ON UserRoleMap.UserId = User.Id
					INNER JOIN Role
					  ON UserRoleMap.RoleId = Role.Id
				ORDER BY
					RoleName,
					FullName'''), lambda row: row.RoleName)
			self.Model.RoleList.append(Grouping("(nenhum)", self.DatabaseCursor.Execute('''
				SELECT
					NULL AS RoleName,
					User.Id AS Id,
					User.FullName AS FullName
				FROM
					User
				WHERE
					User.Id NOT IN (SELECT UserId FROM UserRoleMap)''')))
			return self.RenderTemplate('User/View/User/Index')
		if groupBy == 'Campus':
			self.Model.GroupBy = 'Campus'
			self.Model.CampusList = GroupBy(self.DatabaseCursor.Execute('''
				SELECT
					NULL AS CampusName,
					User.Id AS Id,
					User.FullName AS FullName
				FROM
					UserCampus
					INNER JOIN User
					  ON UserCampus.UserId = User.Id
				WHERE
					UserCampus.CampusId IS NULL
				UNION SELECT
					Campus.Name AS CampusName,
					User.Id AS Id,
					User.FullName AS FullName
				FROM
					UserCampus
					INNER JOIN User
					  ON UserCampus.UserId = User.Id
					INNER JOIN Campus
					  ON UserCampus.CampusId = Campus.Id
				ORDER BY
					CampusName IS NOT NULL,
					CampusName,
					FullName'''), lambda row: row.CampusName)
			for group in self.Model.CampusList:
				if group.Key == None:
					group.Key = "(todas)"
			self.Model.CampusList.append(Grouping("(nenhuma)", self.DatabaseCursor.Execute('''
				SELECT
					NULL AS CampusName,
					User.Id AS Id,
					User.FullName AS FullName
				FROM
					User
				WHERE
					User.Id NOT IN (SELECT UserId FROM UserCampus)''')))
			return self.RenderTemplate('User/View/User/Index')
		else:
			self.Model.GroupBy = None
			self.Model.UserList = self.GetManager(UserManager).GetAll()
			return self.RenderTemplate('User/View/User/Index')

class DetailView (Handler):
	__method__ = 'GET'
	__path__ = '/[Uu]ser/(?P<userId>[1-9][0-9]*)/'

	def Handle(self, *,
			user: User):
		self.RequirePermission(user.path, 'read')

		victimPermissions = self.DatabaseCursor.Execute('''
			SELECT path, permission
			FROM UserPermission
			WHERE UserId = %s''', (user.Id, ))

		self.Model.User = user

		self.Model.IsSwitchingAllowed = self.GetManager(UserManager).IsSwitchingAllowed(user)
		self.Model.IsDeletingAllowed = self.GetManager(UserManager).IsSwitchingAllowed(user)  # TODO
		self.Model.RoleList = self.GetManager(UserRoleManager).GetAll()
		self.Model.EnabledRoleList = self.GetManager(UserRoleManager).GetUserRoles(user)

		return self.RenderTemplate('User/View/User/Detail', victim_permissions = victimPermissions)

class Delete (Handler):
	__method__ = 'POST'
	__path__ = '/User/(?P<userId>[1-9][0-9]*)/Delete/'

	def Handle(self, *,
			user: User):
		self.RequirePermission('/user/', 'admin')

		self.GetManager(UserManager).Delete(user)

		raise NoContent()

class New (Handler):
	__method__ = 'POST'
	__path__ = '/user/=/new/'

	def Handle(self, *,
			FullName: str,
			email: str):
		self.RequirePermission('/user/', 'admin')

		newUser = User(FullName = FullName,
					   email = email)
		newUser = self.GetManager(UserManager).Create(newUser)

		raise SeeOther('/user/%s/' % newUser.Id)

class CreateBulkUsers (Handler):
	__method__ = 'POST'
	__path__ = '/user/=/import-bulk/'

	def Handle(self, *,
			users: bytes):
		self.RequirePermission('/user/', 'admin')

		from openpyxl.reader.excel import load_workbook
		spreadsheetFile = BytesIO(users)
		workbook = load_workbook(spreadsheetFile)
		worksheet = workbook.get_sheet_by_name(name = "Usuários")

		def ImportRow(rowId):
			from collections import namedtuple
			ExcelImportError = namedtuple('ExcelImportError', ['cell', 'value', 'error_type'])
			ref = 'A%d' % rowId
			fullName = worksheet.cell(ref).value
			ref = 'B%d' % rowId
			email = worksheet.cell(ref).value
			ref = 'C%d' % rowId
			if worksheet.cell(ref).value == "(todas)":
				campusList = None
			elif worksheet.cell(ref).value is None:
				campusList = []
			else:
				campusNameList = [campus.strip() for campus in worksheet.cell(ref).value.split(",")]
				campusList = []
				for campusName in campusNameList:
					try:
						campusList.append(self.DatabaseCursor.Single('SELECT Id FROM Campus WHERE Name = %s', (campusName, )))
					except ValueError:
						return [ExcelImportError(cell = ref, value = campusName, error_type = 'invalid_campus')]

			newUser = User(FullName = fullName, email = email)
			victim = self.GetManager(UserManager).Create(newUser)
			if campusList is None:
				self.DatabaseCursor.Execute('''INSERT INTO UserCampus (UserId, CampusId)
								   VALUES (%s, %s)''',
								(victim.Id, None))
			else:
				for campus in campusList:
					self.DatabaseCursor.Execute('''INSERT INTO UserCampus (UserId, CampusId)
									   VALUES (%s, %s)''',
									(victim.Id, campus.Id))
			self.DatabaseCursor.Execute('''INSERT INTO UserPermission (UserId, path, permission)
							   VALUES (%s, %s, %s)''',
							(victim.Id, '/candidate/', 'write'))

			self.ImportedRows += 1
			return []

		self.ImportErrors = []
		currentRowId = 2
		self.ImportedRows = 0
		self.DatabaseCursor.begin()
		while True:
			if not worksheet.cell('A%d' % currentRowId).value:
				break
			self.ImportErrors.extend(ImportRow(currentRowId))
			currentRowId += 1
		self.DatabaseCursor.Commit()
		return self.RenderTemplate('User/View/User/ImportBulk',
							 import_errors = self.ImportErrors,
							 imported_rows = self.ImportedRows)

class DetailEdit (Handler):
	__method__ = 'POST'
	__path__ = '/user/(?P<userId>[1-9][0-9]*)/'

	def Handle(self, *,
			userId: int,
			name: str,
			value: str):
		if not self.HasPermission('/user/', 'admin') and self.CurrentUser.Id != userId:
			raise Forbidden()

		if name not in ('FullName', 'email', 'username', 'password'):
			raise BadRequest()

		if name == 'password':
			if userId != self.CurrentUser.Id:
				raise BadRequest()
			name = 'password_hash'
			value = bcrypt.hashpw(value.encode('utf-8'), bcrypt.gensalt())

		self.DatabaseCursor.Execute('''
			UPDATE User
			SET %s = %%s
			WHERE id = %%s''' % (name, ),
			(value,
			 userId))
		self.DatabaseCursor.Commit()

		raise NoContent()

class Invite (Handler):
	__method__ = 'POST'
	__path__ = '/user/(?P<userId>[1-9][0-9]*)/invite/'

	def Handle(self, *,
			userId: int):
		self.RequirePermission('/user/', 'admin')

		self.GetManager(UserManager).SendInvite(self.GetManager(UserManager).GetById(userId))

		raise NoContent()

class Switch (Handler):
	__method__ = 'POST'
	__path__ = '/User/(?P<VictimId>[1-9][0-9]*)/Switch/'

	def Handle(self, *,
			VictimId: int):
		if not self.GetManager(UserManager).IsSwitchingAllowed(self.GetManager(UserManager).GetById(VictimId)):
			raise Forbidden()

		self.Session.user = VictimId
		raise SeeOther('/')

class Grant (Handler):
	__method__ = 'POST'
	__path__ = '/user/(?P<userId>[1-9][0-9]*)/grant/'

	def Handle(self, *,
			userId: int,
			path: str,
			permission: str):
		self.RequirePermission('/user/', 'admin')

		if permission not in ('none', 'read', 'write', 'admin'):
			raise BadRequest()

		self.DatabaseCursor.Begin()
		self.DatabaseCursor.Execute('''
			DELETE FROM UserPermission
			WHERE UserId = %s AND
				  path = %s''',
			(userId, path))

		if permission != 'none':
			self.DatabaseCursor.Execute('''
				INSERT INTO UserPermission (UserId, path, permission)
				VALUES (%s, %s, %s)''',
				(userId,
				 path,
				 permission))

		self.DatabaseCursor.Commit()
		raise SeeOther('/user/%d/' % userId)

class CampusAttach (Handler):
	__method__ = 'POST'
	__path__ = '/user/(?P<userId>[1-9][0-9]*)/campus/(?P<campusId>[1-9][0-9]*|=)/'

	def Handle(self, *,
			userId: int,
			campus: Campus = None,
			value: bool):
		self.RequirePermission('/user/', 'admin')

		self.DatabaseCursor.Begin()
		if campus is None:
			self.DatabaseCursor.Execute('''
				DELETE FROM UserCampus
				WHERE UserId = %s AND
					  CampusId IS NULL''',
				(userId, ))
		else:
			self.DatabaseCursor.Execute('''
				DELETE FROM UserCampus
				WHERE UserId = %s AND
					  CampusId = %s''',
				(userId, campus.Id))

		if value:
			self.DatabaseCursor.Execute('''
				INSERT INTO UserCampus (UserId, CampusId)
				VALUES (%s, %s)''',
				(userId,
				 campus.Id))

		self.DatabaseCursor.Commit()
		raise NoContent()

class SetRole (Handler):
	__method__ = 'POST'
	__path__ = '/User/(?P<userId>[1-9][0-9]*)/Role/(?P<userRoleId>[1-9][0-9]*|=)/'

	def Handle(self, *,
			user: User,
			userRole: UserRole,
			value: bool):
		self.RequirePermission('/user/', 'admin')

		self.GetManager(UserManager).SetRole(user, userRole, value)

		raise NoContent()

class Picture (Handler):
	__method__ = 'GET'
	__path__ = '/user/(?P<userId>[1-9][0-9]*)/picture/'

	def Handle(self, *,
			userId: int):
		self.RequireLogin()

		try:
			userPicture = self.GetManager(UserManager).GetPicture(userId)
		except ValueError:
			raise Found('/static/no-photo.png')

		self.ContentType = 'image/jpeg'
		return userPicture

class EditPicture (Handler):
	__method__ = 'POST'
	__path__ = '/user/(?P<userId>[1-9][0-9]*)/picture/'

	def Handle(self, *,
			user: User,
			photo: bytes,
			photo_url: str = None):
		self.RequirePermission(user.path, 'write')

		if photo is not None:
			photoData = photo['data']
		elif photo_url is not None:
			photoFile = urllib.request.urlopen(photo_url)
			photoData = photoFile.read()
			photoFile.close()
		else:
			photoData = self.Environment['wsgi.input'].read()
		croppedFace = DetectFace(photoData)
		if not croppedFace:
			croppedFace = photoData  # FIXME
#            raise SeeOther('/user/%d/picture/edit/', {'face_found': False})
		self.DatabaseCursor.Execute('''
			UPDATE User
			SET picture = %s
			WHERE Id = %s''',
			(croppedFace, user.Id))
		raise SeeOther('/user/%d/' % user.Id)
