from django.shortcuts import render, redirect
from django.views import View
from django.contrib.auth import authenticate, login, logout
from django.contrib import messages
from django.urls import reverse_lazy
from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
from django.db import transaction
from django.http import JsonResponse
import json
from datetime import datetime
from .models import *
from django.contrib.auth.models import User
from .forms import *
from .allocator import SPA
from.LatexGen import mappedResult
from django_drf_filepond.api import store_upload, delete_stored_upload
from django_drf_filepond.models import TemporaryUpload, StoredUpload


# View to handle login page
class LoginView(View):
	template_name = 'login.html'
	
	def get(self,request,*args,**kwargs):
		# Render login page
		return render(request,self.template_name)

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

		# Get details from the post request
		username = request.POST.get('username')
		password = request.POST.get('password')

		user  = authenticate(request,username=username,password=password)

		if user is not None:
			# user exists
			login(request,user)
			return redirect('gap:dashboard')
		else:
			messages.error(request,'Username or password is incorrect')
			return render(request,self.template_name)


# Function to logout a user
@login_required(login_url = 'gap:login')
def LogoutView(request):
	logout(request)
	return redirect('gap:login')


# Dashboard for all users
class dashboardView(LoginRequiredMixin,UserPassesTestMixin,View):
	login_url = reverse_lazy('gap:login')
	redirect_field_name = 'next'

	def test_func(self):
		# used to set template name based on type of user
		self.user_type = get_user_type(self.request.user)
		if self.user_type == 'student':
			self.template_name = 'student_dashboard.html'
		else:
			self.template_name = 'faculty_dashboard.html'
		return True

	def get(self,request,*args,**kwargs):
		# Render dashboard based on type of user
		context= get_context(self.user_type,request)
		return render(request,self.template_name,context)

	def post(self,request,*args,**kwargs):
		if self.user_type == 'student':
			# Handling student post requests
			user_application = StudentDetailModel.objects.filter(student=request.user)
			if user_application.exists():
				# Update existing data
				user_application = user_application.first()
				form = StudentDetailModelForm(request.POST,instance=user_application)
				education_detail = StudentEducationFormset(request.POST,instance=user_application)
				project_detail = StudentProjectFormset(request.POST,instance=user_application)
			else:
				# Create new model row
				form = StudentDetailModelForm(request.POST)
				education_detail = StudentEducationFormset(request.POST)
				project_detail = StudentProjectFormset(request.POST)
			if form.is_valid():
				# Form validated
				self.object = form.save(commit=False)
				self.object.student = request.user
				# Save the main form , so it can be used for dependant models
				self.object.save()

				# Save education details of student
				with transaction.atomic():
					# Refer to the main model
					education_detail.instance = self.object
					if education_detail.is_valid():
						ids = get_ids(request.POST,'education')
						self.object.education.exclude(id__in=ids).delete()
						education_detail.save()
					else:
						messages.error(request,education_detail.errors,extra_tags='Education Details')

				# Save project details of student
				with transaction.atomic():
					# Refer to the main model
					project_detail.instance = self.object
					if project_detail.is_valid():
						ids = get_ids(request.POST,'projects')
						self.object.projects.exclude(id__in=ids).delete()
						project_detail.save()
					else:
						messages.error(request,project_detail.errors,extra_tags='Project Details')
				
				# Handle file upload 
				upload_id = request.POST.get('filepond')
				user_application = StudentDetailModel.objects.get(student=request.user)
				resume_record = StudentResumeModel.objects.filter(student=user_application)
				if resume_record and resume_record.count() != 0:
					# Handle already uploaded previous resume
					resume_record = resume_record.first()
					curr_upload_id = resume_record.file.upload_id 
					if upload_id == "":
						# Delete the resume if user has requested to delete it
						resume_record.delete()
						delete_stored_upload(curr_upload_id,delete_file=True)
					elif curr_upload_id != upload_id:
						# Update previous resume if new one is different from old resume
						temp_upload = TemporaryUpload.objects.get(upload_id=upload_id)
						file_reference = store_upload(upload_id,destination_file_path=str(request.user.username) + "/"+ temp_upload.upload_name )
						resume_record.file = file_reference
						resume_record.save()
						delete_stored_upload(curr_upload_id,delete_file=True)
				else:
					if upload_id != "":
						# Create new resume model row
						temp_upload = TemporaryUpload.objects.get(upload_id=upload_id)
						file_reference = store_upload(upload_id,destination_file_path=str(request.user.username) + "/"+ temp_upload.upload_name )
						resume_record = StudentResumeModel(student=user_application,file=file_reference)
						resume_record.save()
			else:
				messages.error(request,form.errors,extra_tags='main_form')
			if form.is_valid() and education_detail.is_valid() and project_detail.is_valid():
				return redirect('gap:dashboard')
			context = get_context(self.user_type,request)
			return render(request,self.template_name,context)
		else:

			# Handle faculty and facad users
			if 'roundDetailUpdate' in request.POST and self.user_type == 'facad':
				# Update round details
				# Restricted only to facad
				roundDetail = RoundDetailsModelForm(request.POST,instance=RoundDetailsModel.objects.get(roundNo=request.POST['roundNo']))
				if roundDetail.is_valid():
					roundDetail.save()
					return redirect('gap:dashboard')
				else:
					messages.error(request,roundDetail.errors)
					context = get_context(self.user_type,request)
					return render(request,self.template_name,context)
			

			if request.POST.get('positionId'):
				# Update the already present open positions
				position = PositionsModel.objects.get(id=request.POST.get('positionId'))
				form = PositionsModelForm(request.POST,instance=position)
				requisite_lists = RequisitesFormset(request.POST,instance=position)
			else:
				# Create a new open position
				form = PositionsModelForm(request.POST)
				requisite_lists = RequisitesFormset(request.POST)
			errors = form.errors
			if form.is_valid():
				self.object = form.save(commit=False)
				self.object.faculty = request.user
				self.object.initial_total_positions = self.object.total_positions
				# Save the main form , so it can be used for dependant models
				self.object.save()

				# Save requisite details of project
				with transaction.atomic():
					# Refer to main model
					requisite_lists.instance = self.object
					if requisite_lists.is_valid():
						ids = get_ids(request.POST,'requisites')
						self.object.requisites.exclude(id__in=ids).delete()
						requisite_lists.save()
					else:
						messages.error(request,requisite_lists.errors,extra_tags='Requisite details')
			else:
				messages.error(request,form.errors,extra_tags='Form Details')
			if form.is_valid() and requisite_lists.is_valid():
				return redirect('gap:dashboard')
			messages.error(request,errors)
			context = get_context(self.user_type,request)
			return render(request,self.template_name,context)
				

# View to handle shorlisting request
@login_required(login_url = 'gap:login')
def shortlistView(request):
	user_type = get_user_type(request.user)
	if request.method == 'POST':
		# sanity check to avoid get requests
		if user_type== 'student':
			# Handle student shortlist requests
			if not StudentDetailModel.objects.get(student=request.user).alloted_project:
				student = StudentDetailModel.objects.get(student=request.user)
				project = PositionsModel.objects.get(id=request.POST.get('project_Id'))
				
				# Handle both shortlisting and discarding
				if request.POST.get('action')=="shortlist":
					if not StudentPriorities.objects.filter(student=student,project=project).exists():
						current_priority = student.priorities.count()
						StudentPriorities.objects.create(student=student,project=project,current_priority=current_priority)
					else:
						# Handle case where student has already shortlisted the project
						messages.error(request,"The application you are trying to shortlist is already shortlisted")
						context = get_context(user_type,request)
						return render(request,'student_dashboard.html',context)
				elif request.POST.get('action')=="discard":
					if StudentPriorities.objects.filter(student=student,project=project).exists():
						priority = StudentPriorities.objects.filter(student=student,project=project).first()
						updatePrioritiesAfterDeletion(user_type,student,project,priority.current_priority)
						priority.delete()
					else:
						# Handle case where student has already discarded the project
						messages.error(request,"The application you are trying to discard is not shortlisted by you")
						context = get_context(user_type,request)
						return render(request,'student_dashboard.html',context)
			else:
				# Handle case where student is already a project
				messages.error(request,"You cannot apply for any positions as you are already allocated")
				context = get_context(user_type,request)
				return render(request,'student_dashboard.html',context)
		else:
			# Handle project shortlist requests
			student = StudentDetailModel.objects.get(id=request.POST.get('student_Id'))
			if student.alloted_project:
				# Handle case where student is already a project
				messages.error(request,"The application you are trying to shortlist is already alloted to another project")
				context = get_context(user_type,request)
				return render(request,'faculty_dashboard.html',context)
			project = PositionsModel.objects.get(id=request.POST.get('project_Id'))
			if request.POST.get('action')=="shortlist":
				if not ProjectPriorities.objects.filter(student=student,project=project).exists():
					current_priority = project.priorities.count()
					ProjectPriorities.objects.create(student=student,project=project,current_priority=current_priority)
				else:
					# Handle case where project has already shortlisted the student
					messages.error(request,"The application you are trying to shortlist is already shortlisted")
					context = get_context(user_type,request)
					return render(request,'faculty_dashboard.html',context)
			elif request.POST.get('action')=="discard":
				if ProjectPriorities.objects.filter(student=student,project=project).exists():
					priority = ProjectPriorities.objects.filter(student=student,project=project).first()
					updatePrioritiesAfterDeletion(user_type,student,project,priority.current_priority)
					priority.delete()
				else:
					# Handle case where project has already discarded the student
					messages.error(request,"The application you are trying to discard is not shortlisted by you")
					context = get_context(user_type,request)
					return render(request,'faculty_dashboard.html',context)

	return redirect('gap:dashboard')


# Update priorities of all the users
@login_required(login_url = 'gap:login')
def updatePriorityView(request):
	if get_user_type(request.user) == 'student':
		# Handles priorities sent by students
		if StudentDetailModel.objects.get(student=request.user).alloted_project:
			# Handles the case where student is already alloted a project
			return JsonResponse({'status_code':400,'detail':'You are already alloted a project'})

		# Get priority list from post request
		priority_list = json.loads(request.POST.get('priorities'))['priority_list']
		clauses = ' '.join(['WHEN project_id={} THEN {}'.format(pk, i) for i, pk in enumerate(priority_list)])
		ordering = 'CASE {} END'.format(clauses)
		priorities = StudentDetailModel.objects.get(student=request.user).priorities.all().extra(select={'ordering': ordering}, order_by=('ordering',))
		if priorities.exists():
			# Update priorities
			for index,priority in enumerate(priorities):
				priority.current_priority = index
				priority.save()
			return JsonResponse({'status_code':200,'successMessage':'Priorities are updated successfully'})
		else:
			return JsonResponse({'status_code':403,'detail':'You do not have permission to update priorities'})
	elif get_user_type(request.user) in ['faculty','facad']:
		# Handles priorities sent by faculties and facad
		
		# Get priority list from post request
		priority_list = json.loads(request.POST.get('priorities'))['priority_list']
		project_id = request.POST.get('projectId')
		clauses = ' '.join(['WHEN student_id={} THEN {}'.format(pk, i) for i, pk in enumerate(priority_list)])
		ordering = 'CASE {} END'.format(clauses)

		priorities = ProjectPriorities.objects.filter(project_id=project_id).extra(select={'ordering': ordering}, order_by=('ordering',))
		if priorities.exists():
			for index,priority in enumerate(priorities):
				# Update priorities
				priority.current_priority = index
				priority.save()
			return JsonResponse({'status_code':200,'successMessage':'Priorities are updated successfully','title':PositionsModel.objects.get(id=project_id).title})
		else:
			return JsonResponse({'status_code':403,'detail':'You do not have permission to update priorities','title':PositionsModel.objects.get(id=project_id).title})	
	return JsonResponse({'status_code':403,'detail':'You do not have permission to update priorities','title':PositionsModel.objects.get(id=project_id).title})


# Handles round ending by facad
@login_required(login_url = 'gap:login')
def generateMappingsView(request):
	# Get student preferences
	student_P, student_MS = get_preferences(pref_type='student')
	print(student_P)
	# Get project preferences
	project_P, project_MS,project_C = get_preferences(pref_type='project')

	# Run the algorithm to generate mappings
	allocations = SPA(student_P,project_P,project_C,student_MS,project_MS,[])

	current_round = RoundDetailsModel.objects.latest('roundNo')
	generated_mappings = []
	unallocated_count, allocated_count = 0, 0

	# Update the tables and create new rows for generated mappings
	for student in allocations:
		print(student,allocations[student])
		if len(allocations[student]) == 0:
			unallocated_count += 1
		else:
			allocated_count += 1
			project = PositionsModel.objects.get(id=allocations[student][0])
			student = StudentDetailModel.objects.get(id=student)
			student.alloted_project = project
			student.save()
			project.total_positions -= 1 
			project.save()
			generated_mappings.append(GeneratedMappingsModel(roundNo=current_round.roundNo,project=project,faculty=project.faculty,student=student)) 
	
	GeneratedMappingsModel.objects.bulk_create(generated_mappings)
	current_round.unallocated = unallocated_count
	current_round.allocated = allocated_count
	current_round.endDate = datetime.now()
	current_round.save()

	# Create a new round
	RoundDetailsModel(roundNo = current_round.roundNo + 1 ).save()


	# data archiving
	archived_student_priorities = []
	for priority in StudentPriorities.objects.all():
		archive_priority = StudentPrioritiesArchive()
		for field in priority._meta.fields:
			setattr(archive_priority,field.name,getattr(priority,field.name))
		archive_priority.roundNo = current_round.roundNo
		archived_student_priorities.append(archive_priority)
		priority.delete()
	StudentPrioritiesArchive.objects.bulk_create(archived_student_priorities)

	del archived_student_priorities

	archived_project_priorities = []
	for priority in ProjectPriorities.objects.all():
		archive_priority = ProjectPrioritiesArchive()
		for field in priority._meta.fields:
			setattr(archive_priority,field.name,getattr(priority,field.name))
		archive_priority.roundNo = current_round.roundNo
		archived_project_priorities.append(archive_priority)
		priority.delete()
	ProjectPrioritiesArchive.objects.bulk_create(archived_project_priorities)
	
	# Generate pdf of mappings for all rounds
	generate_pdf()

	return redirect('gap:dashboard')


# Helper codes
def get_preferences(pref_type):
	'''
	Input:
	pref_type: Student, faculty

	Output:
	Ordered list of priorities pertaining to all the users of that type
	'''
	result = {}
	emptyresult = {}
	counts = {}
	if pref_type=='student':
		students = StudentPriorities.objects.values_list('student',flat=True).distinct()
		for student in students:
			emptyresult[student] = []
			result[student] = list(StudentPriorities.objects.filter(student=student).order_by('current_priority').values_list('project',flat=True))
		return result, emptyresult
	else:
		projects = ProjectPriorities.objects.values_list('project',flat=True).distinct()
		for project in projects:
			emptyresult[project] = []
			counts[project] = PositionsModel.objects.get(id=project).total_positions
			result[project]  = list(ProjectPriorities.objects.filter(project=project).order_by('current_priority').values_list('student',flat=True))
		return result, emptyresult , counts

def get_user_type(user):
	'''
	Input:
	User: user object

	Output:
	Type of user
	'''
	if is_faculty(user):
		if is_facad(user):
			return 'facad'
		return 'faculty'
	return 'student'


def is_faculty(user):
	# Function to check if user is a faculty
    return user.groups.filter(name='faculty').exists()

def is_facad(user):
	# Function to check if user is  a facad
    return user.groups.filter(name='facad').exists()


def get_context(user_type,request):
	context = {}
	if not RoundDetailsModel.objects.all().exists():
		# Create a new round if it does not exist
		RoundDetailsModel().save()

	# Get current round details
	current_round = RoundDetailsModel.objects.latest('roundNo')
	context['current_round'] = current_round
	if user_type == 'student':
		# Get student context details

		# User application with all student details
		user_application = StudentDetailModel.objects.filter(student=request.user)
		print(user_application)
		if user_application.exists():
			# All application details and prefilled forms
			context['applicationDetails'] = user_application.first()
			context['projectDetails'] = user_application.first().projects.all()
			context['educationDetails'] = user_application.first().education.all()
			context['ResumeDetails'] = user_application.first().resume.first()
			context['studentDetailForm'] = StudentDetailModelForm(None,instance=user_application.first())
			context['studentProjects'] = StudentProjectFormset(None,instance=user_application.first())
			context['studentEducation'] = StudentEducationFormset(None,instance=user_application.first())
			selected_vals = []
			for project in user_application.first().projects.all():
				selected_vals.append(project.languages)
			context['selected_project_languagues'] = selected_vals
		else:
			# Empty forms as user has not created his profile
			context['studentDetailForm'] = StudentDetailModelForm()
			context['studentProjects'] = StudentProjectFormset()
			context['studentEducation'] = StudentEducationFormset()

		# get list of applied and unapplied positions based on priority value
		positions = PositionsModel.objects.filter(total_positions__gt=0)
		if user_application.exists():
			ids = user_application.first().priorities.order_by('current_priority').values_list('project', flat=True)
		else:
			ids = StudentDetailModel.objects.none()
		clauses = ' '.join(['WHEN id={} THEN {}'.format(pk, i) for i, pk in enumerate(ids)])
		ordering = 'CASE {} END'.format(clauses)

		# List of all unapplied projects
		context['unappliedPositions'] = positions.exclude(id__in=ids)
		
		# List of all applied projects if it exists
		if ids.exists():
			context['appliedPositions'] = positions.filter(id__in=ids).extra(select={'ordering': ordering}, order_by=('ordering',))
		else:
			context['appliedPositions'] = None	
	else:
		# Get context of faculty

		# Prefilled forms
		context['user_type'] = user_type
		context['requisitesForm'] = RequisitesFormset()
		context['newPosition'] = PositionsModelForm()
		context['availablePositions'] = PositionsModel.objects.filter(faculty=request.user)
		updateforms = []
		updaterequisiteforms = []

		# List of all projects proposed by faculty
		for position in context['availablePositions']:
			updateforms.append(PositionsModelForm(None,instance=position))
			updaterequisiteforms.append(RequisitesFormset(None,instance=position))
		context['updateforms'] = updateforms
		context['updaterequisiteforms'] = updaterequisiteforms

		# list of all un shortlisted applications
		priorities = ProjectPriorities.objects.filter(project__in=context['availablePositions'])
		exclude_conditions = priorities.values('student','project').distinct()
		allapplications = StudentPriorities.objects.filter(project__in=context['availablePositions'])
		for condition in exclude_conditions:
			allapplications = allapplications.exclude(student=condition['student'],project=condition['project'])
		context['allApplications'] = allapplications

		# List of all priorities associated with each project
		priority_groups = dict()
		for priority in ProjectPriorities.objects.filter(project__in=context['availablePositions']).order_by('-current_priority'):
			priority_groups.setdefault(priority.project,[]).append(priority)
		context['ProjectWiseShortlistedApplications'] = list(priority_groups.values())
		
		# List of all alloted students
		context['allotedStudents'] = GeneratedMappingsModel.objects.filter(faculty=request.user)

		if user_type == "facad":
			# Additional Data visible only to facad
			print(RoundDetailsModel)
			if not RoundDetailsModel.objects.all().exists():
				RoundDetailsModel().save()

			# Round update forms and details
			context['RoundUpdateform'] = RoundDetailsModelForm(None,instance=RoundDetailsModel.objects.latest('roundNo'))
			context['roundDetails'] = RoundDetailsModel.objects.all()
			context['AllotmentsSoFar'] = GeneratedMappingsModel.objects.all()

	return context

def get_ids(data,prefix):
	# Get a list of all ids from post request based on specific prefix
	count = data[prefix + '-TOTAL_FORMS']
	ids = []
	for index in range(int(count)):
		try:
			curr_id = data[prefix + '-{}-id'.format(index)]
			if curr_id: 
				ids.append(int(curr_id))
		except:
			break
	return ids

def updatePrioritiesAfterDeletion(user_type,student,project,current_priority):
	# Function to update the priorities incase deletion is done not from end or start of the list
	if user_type == 'student':
		priorities = StudentPriorities.objects.filter(student=student,project=project).filter(current_priority__gt=current_priority)
	else:
		priorities = ProjectPriorities.objects.filter(student=student,project=project).filter(current_priority__gt=current_priority)
	with transaction.atomic():
		# Update priorities
		for priority in priorities:
			priority.current_priority = current_priority
			current_priority += 1
			priority.save()

# Function to generate pdf
def generate_pdf():
	faculties = PositionsModel.objects.values_list('faculty',flat=True).distinct()
	mappings = {}
	guide_info = {}

	# Extract data needed for latex file
	for faculty_id in faculties:
		faculty = User.objects.get(id=faculty_id)
		requirement_count = 0
		projects = PositionsModel.objects.filter(faculty=faculty).values_list('id',flat=True)
		faculty_allocation = {}

		# project associated with particular faculty
		for project_id in projects:
			project = PositionsModel.objects.get(id=project_id)
			requirement_count += project.initial_total_positions
			alloted_students = project.student_mappings.all()
			project_allocation = {}

			# student allocated to each project
			for student in alloted_students:
				project_allocation[student.student.id] = student.student.student.username
			faculty_allocation[project.id] = project_allocation
		mappings[faculty.username] = faculty_allocation
		guide_info[faculty.username] = {"name":faculty.get_full_name(),"requirement":requirement_count}

	print(mappings)
	print(guide_info)
	# Storage location for the file
	filelocation = "guideAllocationPortal/static/guideAllocationPortal/allocationResults"
	mappedResult(mappings,guide_info,StudentDetailModel.objects.all().count(),filelocation)