# 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
		dufault_working_hours = settings.dufault_working_hours
		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'),
				"hour":dufault_working_hours,
			})

		# Return employees after fetching
		return employees

	@frappe.whitelist()
	def create_attendance(self):
		"""
		A function to implement all accounts and create the required records.
		"""
		timesheet_count = 0
		additional_salary_count = 0
		settings = get_settings()
		print("*"*50,self.timesheets_created)
		# 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 all([salary_component_overtime_h, salary_component_overtime_n, salary_component_deduction]):
			frappe.throw(_("Please configure the Salary Components in Timesheet Tool Settings."))  

		for detail in self.timesheet_tool_details:
			try:
				if detail.hour:
					# total_working_hours
					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

			except Exception as e:
				frappe.log_error(f"Error processing detail for employee {detail.employee}: {str(e)}")
				frappe.msgprint(_("Error processing detail for employee {}: {}").format(detail.employee, str(e)))

		# Update timesheets_created to True after successfully creating the records
		self.db_set("timesheets_created", 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):
	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,
		"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):
	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.overwrite_salary_structure_amount = 0
	additional_salary.insert(ignore_permissions=True)