# Copyright (c) 2015, Xhive LLC
# License: GNU General Public License v3. See license.txt


import xhiveframework
from xhiveframework import _
from xhiveframework.model.document import Document
from xhiveframework.query_builder import Criterion
from xhiveframework.utils import (
	add_days,
	cint,
	cstr,
	format_date,
	get_datetime,
	get_link_to_form,
	getdate,
	nowdate,
)

from hrms.hr.doctype.shift_assignment.shift_assignment import has_overlapping_timings
from hrms.hr.utils import get_holiday_dates_for_employee, validate_active_employee


class DuplicateAttendanceError(xhiveframework.ValidationError):
	pass


class OverlappingShiftAttendanceError(xhiveframework.ValidationError):
	pass


class Attendance(Document):
	def validate(self):
		from xhiveerp.controllers.status_updater import validate_status

		validate_status(self.status, ["Present", "Absent", "On Leave", "Half Day", "Work From Home"])
		validate_active_employee(self.employee)
		self.validate_attendance_date()
		self.validate_duplicate_record()
		self.validate_overlapping_shift_attendance()
		self.validate_employee_status()
		self.check_leave_record()

	def on_cancel(self):
		self.unlink_attendance_from_checkins()

	def validate_attendance_date(self):
		date_of_joining = xhiveframework.db.get_value("Employee", self.employee, "date_of_joining")

		# leaves can be marked for future dates
		if (
			self.status != "On Leave"
			and not self.leave_application
			and getdate(self.attendance_date) > getdate(nowdate())
		):
			xhiveframework.throw(
				_("Attendance can not be marked for future dates: {0}").format(
					xhiveframework.bold(format_date(self.attendance_date)),
				)
			)
		elif date_of_joining and getdate(self.attendance_date) < getdate(date_of_joining):
			xhiveframework.throw(
				_("Attendance date {0} can not be less than employee {1}'s joining date: {2}").format(
					xhiveframework.bold(format_date(self.attendance_date)),
					xhiveframework.bold(self.employee),
					xhiveframework.bold(format_date(date_of_joining)),
				)
			)

	def validate_duplicate_record(self):
		duplicate = get_duplicate_attendance_record(
			self.employee, self.attendance_date, self.shift, self.name
		)

		if duplicate:
			xhiveframework.throw(
				_("Attendance for employee {0} is already marked for the date {1}: {2}").format(
					xhiveframework.bold(self.employee),
					xhiveframework.bold(format_date(self.attendance_date)),
					get_link_to_form("Attendance", duplicate[0].name),
				),
				title=_("Duplicate Attendance"),
				exc=DuplicateAttendanceError,
			)

	def validate_overlapping_shift_attendance(self):
		attendance = get_overlapping_shift_attendance(
			self.employee, self.attendance_date, self.shift, self.name
		)

		if attendance:
			xhiveframework.throw(
				_("Attendance for employee {0} is already marked for an overlapping shift {1}: {2}").format(
					xhiveframework.bold(self.employee),
					xhiveframework.bold(attendance.shift),
					get_link_to_form("Attendance", attendance.name),
				),
				title=_("Overlapping Shift Attendance"),
				exc=OverlappingShiftAttendanceError,
			)

	def validate_employee_status(self):
		if xhiveframework.db.get_value("Employee", self.employee, "status") == "Inactive":
			xhiveframework.throw(_("Cannot mark attendance for an Inactive employee {0}").format(self.employee))

	def check_leave_record(self):
		leave_record = xhiveframework.db.sql(
			"""
			select leave_type, half_day, half_day_date
			from `tabLeave Application`
			where employee = %s
				and %s between from_date and to_date
				and status = 'Approved'
				and docstatus = 1
		""",
			(self.employee, self.attendance_date),
			as_dict=True,
		)
		if leave_record:
			for d in leave_record:
				self.leave_type = d.leave_type
				if d.half_day_date == getdate(self.attendance_date):
					self.status = "Half Day"
					xhiveframework.msgprint(
						_("Employee {0} on Half day on {1}").format(self.employee, format_date(self.attendance_date))
					)
				else:
					self.status = "On Leave"
					xhiveframework.msgprint(
						_("Employee {0} is on Leave on {1}").format(self.employee, format_date(self.attendance_date))
					)

		if self.status in ("On Leave", "Half Day"):
			if not leave_record:
				xhiveframework.msgprint(
					_("No leave record found for employee {0} on {1}").format(
						self.employee, format_date(self.attendance_date)
					),
					alert=1,
				)
		elif self.leave_type:
			self.leave_type = None
			self.leave_application = None

	def validate_employee(self):
		emp = xhiveframework.db.sql(
			"select name from `tabEmployee` where name = %s and status = 'Active'", self.employee
		)
		if not emp:
			xhiveframework.throw(_("Employee {0} is not active or does not exist").format(self.employee))

	def unlink_attendance_from_checkins(self):
		EmployeeCheckin = xhiveframework.qb.DocType("Employee Checkin")
		linked_logs = (
			xhiveframework.qb.from_(EmployeeCheckin)
			.select(EmployeeCheckin.name)
			.where(EmployeeCheckin.attendance == self.name)
			.for_update()
			.run(as_dict=True)
		)

		if linked_logs:
			(
				xhiveframework.qb.update(EmployeeCheckin)
				.set("attendance", "")
				.where(EmployeeCheckin.attendance == self.name)
			).run()

			xhiveframework.msgprint(
				msg=_("Unlinked Attendance record from Employee Checkins: {}").format(
					", ".join(get_link_to_form("Employee Checkin", log.name) for log in linked_logs)
				),
				title=_("Unlinked logs"),
				indicator="blue",
				is_minimizable=True,
				wide=True,
			)


def get_duplicate_attendance_record(employee, attendance_date, shift, name=None):
	attendance = xhiveframework.qb.DocType("Attendance")
	query = (
		xhiveframework.qb.from_(attendance)
		.select(attendance.name)
		.where((attendance.employee == employee) & (attendance.docstatus < 2))
	)

	if shift:
		query = query.where(
			Criterion.any(
				[
					Criterion.all(
						[
							((attendance.shift.isnull()) | (attendance.shift == "")),
							(attendance.attendance_date == attendance_date),
						]
					),
					Criterion.all(
						[
							((attendance.shift.isnotnull()) | (attendance.shift != "")),
							(attendance.attendance_date == attendance_date),
							(attendance.shift == shift),
						]
					),
				]
			)
		)
	else:
		query = query.where((attendance.attendance_date == attendance_date))

	if name:
		query = query.where(attendance.name != name)

	return query.run(as_dict=True)


def get_overlapping_shift_attendance(employee, attendance_date, shift, name=None):
	if not shift:
		return {}

	attendance = xhiveframework.qb.DocType("Attendance")
	query = (
		xhiveframework.qb.from_(attendance)
		.select(attendance.name, attendance.shift)
		.where(
			(attendance.employee == employee)
			& (attendance.docstatus < 2)
			& (attendance.attendance_date == attendance_date)
			& (attendance.shift != shift)
		)
	)

	if name:
		query = query.where(attendance.name != name)

	overlapping_attendance = query.run(as_dict=True)

	if overlapping_attendance and has_overlapping_timings(shift, overlapping_attendance[0].shift):
		return overlapping_attendance[0]
	return {}


@xhiveframework.whitelist()
def get_events(start, end, filters=None):
	events = []

	employee = xhiveframework.db.get_value("Employee", {"user_id": xhiveframework.session.user})

	if not employee:
		return events

	from xhiveframework.desk.reportview import get_filters_cond

	conditions = get_filters_cond("Attendance", filters, [])
	add_attendance(events, start, end, conditions=conditions)
	return events


def add_attendance(events, start, end, conditions=None):
	query = """select name, attendance_date, status
		from `tabAttendance` where
		attendance_date between %(from_date)s and %(to_date)s
		and docstatus < 2"""
	if conditions:
		query += conditions

	for d in xhiveframework.db.sql(query, {"from_date": start, "to_date": end}, as_dict=True):
		e = {
			"name": d.name,
			"doctype": "Attendance",
			"start": d.attendance_date,
			"end": d.attendance_date,
			"title": cstr(d.status),
			"docstatus": d.docstatus,
		}
		if e not in events:
			events.append(e)


def mark_attendance(
	employee,
	attendance_date,
	status,
	shift=None,
	leave_type=None,
	ignore_validate=False,
	late_entry=False,
	early_exit=False,
):
	if get_duplicate_attendance_record(employee, attendance_date, shift):
		return

	if get_overlapping_shift_attendance(employee, attendance_date, shift):
		return

	company = xhiveframework.db.get_value("Employee", employee, "company")
	attendance = xhiveframework.get_doc(
		{
			"doctype": "Attendance",
			"employee": employee,
			"attendance_date": attendance_date,
			"status": status,
			"company": company,
			"shift": shift,
			"leave_type": leave_type,
			"late_entry": late_entry,
			"early_exit": early_exit,
		}
	)
	attendance.flags.ignore_validate = ignore_validate
	attendance.insert()
	attendance.submit()
	return attendance.name


@xhiveframework.whitelist()
def mark_bulk_attendance(data):
	import json

	if isinstance(data, str):
		data = json.loads(data)
	data = xhiveframework._dict(data)
	company = xhiveframework.get_value("Employee", data.employee, "company")
	if not data.unmarked_days:
		xhiveframework.throw(_("Please select a date."))
		return

	for date in data.unmarked_days:
		doc_dict = {
			"doctype": "Attendance",
			"employee": data.employee,
			"attendance_date": get_datetime(date),
			"status": data.status,
			"company": company,
		}
		attendance = xhiveframework.get_doc(doc_dict).insert()
		attendance.submit()


@xhiveframework.whitelist()
def get_unmarked_days(employee, from_date, to_date, exclude_holidays=0):
	joining_date, relieving_date = xhiveframework.get_cached_value(
		"Employee", employee, ["date_of_joining", "relieving_date"]
	)

	from_date = max(getdate(from_date), joining_date or getdate(from_date))
	to_date = min(getdate(to_date), relieving_date or getdate(to_date))

	records = xhiveframework.get_all(
		"Attendance",
		fields=["attendance_date", "employee"],
		filters=[
			["attendance_date", ">=", from_date],
			["attendance_date", "<=", to_date],
			["employee", "=", employee],
			["docstatus", "!=", 2],
		],
	)

	marked_days = [getdate(record.attendance_date) for record in records]

	if cint(exclude_holidays):
		holiday_dates = get_holiday_dates_for_employee(employee, from_date, to_date)
		holidays = [getdate(record) for record in holiday_dates]
		marked_days.extend(holidays)

	unmarked_days = []

	while from_date <= to_date:
		if from_date not in marked_days:
			unmarked_days.append(from_date)

		from_date = add_days(from_date, 1)

	return unmarked_days
