# Copyright (c) 2024, Ebkar Technology And Management Solutions. and contributors
# For license information, please see license.txt
# timesheet_tool.py

import frappe
from frappe.model.document import Document
from frappe import _
import datetime

from time_sheet_tool.time_sheet_tool.utils.timesheet_tool import check_missing_records

class TimesheetTool(Document): 
	@frappe.whitelist()
	def clear_timesheet_tool_details_table(self):
		# Clear the Timesheet Tool Details table
		self.set("timesheet_tool_details", [])

	@frappe.whitelist()
	def get_all_employees(self, project=None):
		settings = get_settings()
		default_activity = settings.dufault_activity
		ratio_attendance = settings.ratio_attendance

		self.clear_timesheet_tool_details_table()

		# Fetch employees depending on the project
		if project:
			# Fetch employees from Project Team, ensure all conditions are met
			employees = frappe.db.sql("""
				SELECT DISTINCT 
					pt.employee AS name,
					pt.employee_name,
					ss.hour_rate AS hourly_rate,
					ssa.salary_structure
				FROM 
					`tabProject Team` pt
				INNER JOIN 
					`tabSalary Structure Assignment` ssa ON pt.employee = ssa.employee
				INNER JOIN
					`tabSalary Structure` ss ON ssa.salary_structure = ss.name
				WHERE 
					pt.parent = %s
					AND pt.fetch_to_timesheet_tool = 1
					AND ssa.docstatus = 1  -- Ensure only approved Salary Structure Assignments
					AND ss.docstatus = 1  -- Ensure only approved Salary Structure
					AND ss.salary_slip_based_on_timesheet = 1 -- Ensure timesheet-based salary is required
			""", (project,), as_dict=True)

			if not employees:
				frappe.throw(_("No employees found in the Project Team with a valid Salary Structure Assignment and salary slip based on timesheet for the selected project."))
		else:
			# Fetch active employees directly when no project is selected
			employees = frappe.db.sql("""
				SELECT DISTINCT 
					emp.name AS name,
					emp.employee_name AS employee_name,
					ss.hour_rate AS hourly_rate,
					ssa.salary_structure
				FROM 
					`tabEmployee` emp
				INNER JOIN 
					`tabSalary Structure Assignment` ssa ON emp.name = ssa.employee
				INNER JOIN
					`tabSalary Structure` ss ON ssa.salary_structure = ss.name
				WHERE 
					emp.status = 'Active'
					AND ssa.docstatus = 1  -- Ensure only approved Salary Structure Assignments
					AND ss.docstatus = 1  -- Ensure only approved Salary Structure
					AND ss.salary_slip_based_on_timesheet = 1 -- Ensure timesheet-based salary is required
			""", as_dict=True)

		# Append employees to the child table
		for emp in employees:
			self.append("timesheet_tool_details", {
				"employee": emp.get('name'),
				"employee_name": emp.get('employee_name'),
				"hourly_rate": emp.get('hourly_rate'),
				"attendance": default_activity,
				"ratio_attendance": ratio_attendance,
				"salary_structure":emp.get('salary_structure'),
			})

		# Return employees after fetching
		return employees

	@frappe.whitelist()
	def create_attendance(self):
		"""
		A function to implement all accounts and create the required records.
		"""
		"""
		Calculation of fields required in the timesheet_tool_details
		"""
		timesheet_count = 0
		additional_salary_count = 0
		settings = get_settings()

		# Preparation of Fields of Settings
		salary_component_overtime_h = settings.salary_component_overtime_h
		salary_component_overtime_n = settings.salary_component_overtime_n
		salary_component_deduction = settings.salary_component_deduction
		from_time = f"{self.day} {settings.from_time}"

		# Check for values
		if not salary_component_overtime_h or not salary_component_overtime_n or not salary_component_deduction:
			frappe.throw(_("Please configure the Salary Components in Timesheet Tool Settings."))	
		
		for detail in self.timesheet_tool_details:
				
				if detail.hour:
					totalhours = detail.hour * detail.ratio_attendance
					create_timesheet(self,detail,totalhours,from_time)
					timesheet_count += 1
				#  Additional Salary (Overtime H, Overtime N, Deduction)
				if detail.over_time_h:
						total_over_time_h = detail.over_time_h * settings.h_over_time
						detail.total_payment = total_over_time_h * detail.hourly_rate
						create_additional_salary(self, detail.employee, salary_component_overtime_h, detail.total_payment)
						additional_salary_count += 1
				if detail.over_time_n:
						total_over_time_n = detail.over_time_n * settings.n_over_time
						detail.total_payment = total_over_time_n * detail.hourly_rate
						create_additional_salary(self, detail.employee, salary_component_overtime_n, detail.total_payment)
						additional_salary_count += 1
				if detail.deduction:
						detail.total_payment = detail.deduction * detail.hourly_rate
						create_additional_salary(self, detail.employee, salary_component_deduction,detail.total_payment)
						additional_salary_count += 1
		frappe.msgprint(_("Created {0} Timesheets and {1} Additional Salaries.").format(timesheet_count,additional_salary_count))
	
	@frappe.whitelist()
	def approval_attendance(self):
		"""Approves all associated Timesheets and Additional Salaries."""
		try:
				self.approval_timesheets()
				self.approval_additional_salaries()
				frappe.msgprint(_("All Timesheets and Additional Salaries associated with this document have been approved successfully."))
		except Exception as e:
				frappe.throw(_("An error occurred during approval: {0}").format(str(e)))

	def approval_timesheets(self, cancel=False):
			"""Shared logic for approving or canceling Timesheets."""
			timesheet_list = frappe.get_all("Timesheet",
					filters={
							"timesheet_tool": self.name
					},
			)
			record_num = 0
			for timesheet in timesheet_list:
					document = frappe.get_doc("Timesheet", timesheet.name)
					if not cancel and document.docstatus == 0:  # Approve
							document.submit()
							frappe.db.commit()
							record_num += 1
					elif cancel and document.docstatus == 1:  # Cancel
							document.cancel()
							frappe.db.commit()
							frappe.msgprint(_("Timesheet {0} has been cancelled.").format(document.name))
			frappe.msgprint(_("Timesheet {0} has been approved.").format(record_num))

	def approval_additional_salaries(self, cancel=False):
			"""Shared logic for approving or canceling Additional Salaries."""
			additional_salary_list = frappe.get_all("Additional Salary",
					filters={
							"timesheet_tool": self.name
					},
			)
			record_num = 0
			for additional_salary in additional_salary_list:
					document = frappe.get_doc("Additional Salary", additional_salary.name)
					if not cancel and document.docstatus == 0:  # Approve
							document.submit()
							frappe.db.commit()
							record_num +=1
					elif cancel and document.docstatus == 1:  # Cancel
							document.cancel()
							frappe.db.commit()
			frappe.msgprint(_("Additional Salary {0} has been approved.").format(record_num))
					
	def cancel_attendance(self):
		"""Cancels all associated Timesheets and Additional Salaries."""
		try:
				self.approval_timesheets(cancel=True)
				self.approval_additional_salaries(cancel=True)
				frappe.msgprint(_("All Timesheets and Additional Salaries associated with this document have been cancelled successfully."))
		except Exception as e:
				frappe.throw(_("An error occurred during cancellation: {0}").format(str(e)))

	def on_cancel(self):
			# Cancel Timesheets and Additional Salaries when Timesheet Tool is cancelled.
			self.cancel_attendance()
		
	def on_submit(self):
		self.check_future_day()
		self.check_values_in_timesheet_tool_details() 
		self.check_zero_values_in_timesheet_tool_details()
		self.check_over_time_values()

	def before_save(self):
		pass

	def validate(self):
		self.ensure_unique_employee()
		self.check_limits_in_timesheet_tool_details()

	def check_future_day(self):
		settings = get_settings()
		future_day = settings.future_day
		today = datetime.date.today()
		
		# Turn self.day from text to date object
		try:
			day_date = datetime.datetime.strptime(self.day, "%Y-%m-%d").date()
		except ValueError:
			frappe.throw(_("Invalid date format for day. Please use YYYY-MM-DD."))

		# See if the settings allow future dates.
		if not future_day and day_date > today:
			frappe.throw(_("Company settings do not allow the establishment of a future date schedule"))
	def check_zero_values_in_timesheet_tool_details(self):
		"""Check for zero values in timesheet_tool_details and raise an error if found."""
		error_rows = []
		for idx, detail in enumerate(self.timesheet_tool_details):
			# Check if hourly_rate, over_time_n,  over_time_h and deduction are all zero
			if	detail.ratio_attendance != 0:
				if detail.hour == 0 and detail.over_time_n == 0 and detail.over_time_h == 0 and detail.deduction == 0:
					error_rows.append(idx + 1)  # Save the row number (1-based index)

		if error_rows:
			error_message = _("The following rows have Hour, Over Time N, and Over Time H all equal to 0 in rows: {rows}").format(rows=" , ".join(map(str, error_rows)))
			frappe.throw(error_message)
	def check_values_in_timesheet_tool_details(self):
		if not self.timesheet_tool_details:
			frappe.throw(_("Cannot proceed: No records found in Timesheet Tool Details."))  
	def check_over_time_values(self):
		"""Check that values over_time_n,over_time_h cannot be entered for the staff member on the same date."""
		for idx, row in enumerate(self.timesheet_tool_details):
			if row.over_time_n > 0 and row.over_time_h > 0:
				employee_name = row.employee 
				frappe.throw(
					_("Neither of the values Over Time N, Over Time H can be entered for employee {0} at row {1}.").format(employee_name, idx + 1)
					)
	
	def check_limits_in_timesheet_tool_details(self):
		settings = get_settings()

		max_n_over_time = settings.maximum_n_over_time
		max_h_over_time = settings.maximum_h_over_time
		max_deduction = settings.maximum_deduction
		max_standard_working_hours = settings.maximum_standard_working_hours

		for idx, detail in enumerate(self.timesheet_tool_details):
			if detail.hour is not None and detail.hour > max_standard_working_hours:
				frappe.throw(_(f"Row {idx + 1}: The value of 'Hour' cannot exceed the maximum standard working hours ({max_standard_working_hours})."))
			
			if detail.over_time_n is not None and detail.over_time_n > max_n_over_time:
				frappe.throw(_(f"Row {idx + 1}: The value of 'Over Time N' cannot exceed the maximum allowed overtime hours (normal) ({max_n_over_time})."))
			
			if detail.over_time_h is not None and detail.over_time_h > max_h_over_time:
				frappe.throw(_(f"Row {idx + 1}: The value of 'Over Time H' cannot exceed the maximum allowed overtime hours (holiday) ({max_h_over_time})."))

			if detail.deduction is not None and detail.deduction > max_deduction:
				frappe.throw(_(f"Row {idx + 1}: The value of 'Deduction' cannot exceed the maximum deduction allowed ({max_deduction})."))	
	def ensure_unique_employee(self):
		"""
		Check not to repeat the selection of the same employee in Timesheet Tool Details.
		"""
		timesheet_tool_details = [os.employee for os in self.timesheet_tool_details if os.employee]
		if len(timesheet_tool_details) != len(set(timesheet_tool_details)):
			frappe.throw(_("The same employee cannot be selected more than once."))

def get_settings():
	# Fetch the latest settings
	settings = frappe.get_single("Timesheet Tool Setting")
	return settings  

def create_timesheet(doc, timesheet_tool_details, totalhours, from_time):
    # حساب الوقت النهائي
    to_time = calculate_to_time(from_time, totalhours)

    # Check for overlapping timesheets
    if has_time_overlap(timesheet_tool_details.employee, from_time, to_time, doc.day):
        return  # تم طباعة الرسالة داخل الدالة

    # إنشاء التسجيل الجديد خالية من الأخطاء
    timesheet = frappe.get_doc({
        "doctype": "Timesheet",
        "timesheet_tool": doc.name,
        "company": doc.company,
        "parent_project": doc.project,
        "employee": timesheet_tool_details.employee,
        "time_logs": []
    })

    # إضافة السجلات الزمنية
    child_time_logs = {
        "activity_type": timesheet_tool_details.attendance,
        "hours": totalhours,
        "from_time": from_time,
        "to_time": to_time,  # استخدم الوقت المحسوب
        "project": doc.project,
    }

    timesheet.append("time_logs", child_time_logs)
    timesheet.insert(ignore_permissions=True)
    frappe.db.commit()

def create_additional_salary(doc, employee, salary_component, amount):
    # Verification of additional salary on the same date, staff member and salary component
    if check_existing_additional_salary(doc, employee, doc.day, salary_component):
        frappe.msgprint(_("An Additional Salary for employee {0} on {1} with component {2} already exists.").format(employee, doc.day, salary_component))
        return 
        
    additional_salary = frappe.new_doc("Additional Salary")
    additional_salary.timesheet_tool = doc.name
    additional_salary.company = doc.company
    additional_salary.employee = employee
    additional_salary.payroll_date = doc.day
    additional_salary.salary_component = salary_component
    additional_salary.amount = amount 
    additional_salary.insert(ignore_permissions=True)

def check_existing_timesheet(doc, employee, date):
    # Check the presence of Timesheet in the database
    timesheet_exists = frappe.db.exists("Timesheet", {
            "employee": employee,
            "start_date": date 
    })
    return timesheet_exists

def check_existing_additional_salary(doc, employee, date, salary_component):
    # Verification of additional salary in the database
    additional_salary_exists = frappe.db.exists("Additional Salary", {
            "employee": employee,
            "payroll_date": date,
            "salary_component": salary_component
    })
    return additional_salary_exists
    
def has_time_overlap(employee, from_time, to_time, date):
    overlapping_timesheets = frappe.db.sql("""
        SELECT name FROM `tabTimesheet`
        WHERE employee = %s
        AND start_date = %s
        AND (
            (start_date < %s AND end_date > %s) OR
            (start_date < %s AND end_date > %s) OR
            (start_date = %s OR end_date = %s)
        )
    """, (employee, date, to_time, from_time, from_time, to_time, from_time, to_time), as_dict=True)

    if overlapping_timesheets:
        existing_timesheet_names = ", ".join([ts['name'] for ts in overlapping_timesheets])
        new_timesheet_name = "New Timesheet" 
        frappe.msgprint(_("The following Timesheet records overlap: {0} and {1}").format(existing_timesheet_names, new_timesheet_name))
    
    return bool(overlapping_timesheets)

def calculate_to_time(from_time, totalhours):
    #  Time limit calculation
    start_datetime = datetime.datetime.strptime(from_time, "%Y-%m-%d %H:%M:%S")
    end_datetime = start_datetime + datetime.timedelta(hours=totalhours)
    return end_datetime.strftime("%Y-%m-%d %H:%M:%S")