import frappe
from frappe.utils.safe_exec import safe_eval
from datetime import timedelta, datetime, date
from frappe.utils import getdate
class TaskService:
    
    
    @staticmethod
    def fire_task_triggers(task_names: list[str], event: str, current_triggering_dict=None):
        """Calculate the due date for a list of tasks based on their expected end dates."""
        for task_name in task_names:
            TaskService.fire_task_trigger(task_name, event, current_triggering_dict)
            
            
    @staticmethod
    def get_tasks_by_project(project_name: str):
        """Retrieve all tasks associated with a given project."""
        task_names = frappe.get_all("Task", filters={"project": project_name}, pluck="name")
        tasks = [frappe.get_doc("Task", task_name) for task_name in task_names]
        return tasks
    
    @staticmethod
    def fire_task_trigger(task_name: str, event: str, current_triggering_dict=None):
        """Determine the triggering configuration for a given task."""
        task_type_doc = TaskService.get_task_type_doc(task_name)
        schedule_trigger = task_type_doc.triggering_doctype == current_triggering_dict.get("doctype") if current_triggering_dict else False
        completion_trigger = task_type_doc.custom_completion_trigger_doctype == current_triggering_dict.get("doctype") if current_triggering_dict else False
        match_schedule_event = task_type_doc.trigger == event
        match_completion_event = task_type_doc.custom_completion_trigger == event
        if not schedule_trigger and not completion_trigger:
            print(f"DEBUG: Task {task_name} triggering doctype {task_type_doc.triggering_doctype} does not match triggering doctype {current_triggering_dict.get('doctype')}, skipping calculation.")
            return
        if not match_schedule_event and not match_completion_event:
            print(f"DEBUG: Task {task_name} trigger {task_type_doc.trigger} does not match event {event}, skipping calculation.")
            return
        if task_type_doc.logic_key:
            print(f"DEBUG: Task {task_name} has a logic key set, skipping calculations and running logic.")
            safe_eval(task_type_doc.logic_key, {"task_name": task_name, "task_type_doc": task_type_doc})
            return
        if schedule_trigger:
            triggering_doc_dict = current_triggering_dict if current_triggering_dict else TaskService.get_triggering_doc_dict(task_name=task_name, doctype=task_type_doc.triggering_doctype, task_type_calculate_from=task_type_doc.task_type_calculate_from)
            calculate_from = task_type_doc.calculate_from
            trigger = task_type_doc.trigger
            print(f"DEBUG: Calculating triggering data for Task {task_name} from {calculate_from} on trigger {trigger}")

            calculated_due_date, calculated_start_date = TaskService.calculate_dates(
                task_name=task_name, 
                triggering_doc_dict=triggering_doc_dict, 
                task_type_doc=task_type_doc
                )
            
            update_required = TaskService.determine_due_date_update_required(
                task_name=task_name,
                calculated_due_date=calculated_due_date,
                calculated_start_date=calculated_start_date
            )
            if update_required:
                TaskService.update_task(
                    task_name=task_name,
                    calculated_due_date=calculated_due_date,
                    calculated_start_date=calculated_start_date
                )
        if completion_trigger:
            triggering_doc_dict = current_triggering_dict if current_triggering_dict else TaskService.get_triggering_doc_dict(task_name=task_name, doctype=task_type_doc.custom_completion_trigger_doctype)
            print(f"DEBUG: Running completion trigger logic for Task {task_name}")
            update_required = TaskService.determine_completion_update_required(
                task_name=task_name,
                task_type_doc=task_type_doc,
            )
            if update_required:
                TaskService.update_task(
                    task_name=task_name,
                    status="Completed"
                )
                print(f"DEBUG: Marked Task {task_name} as Completed due to completion trigger.")
            
    
    @staticmethod
    def get_task_type_doc(task_name: str):
        task_type_name = frappe.get_value("Task", task_name, "type")
        return frappe.get_doc("Task Type", task_type_name)
    
    @staticmethod
    def calculate_dates(task_name: str, triggering_doc_dict: dict, task_type_doc) -> tuple[date | None, date | None]:
        offset_direction = task_type_doc.offset_direction
        offset_days = task_type_doc.offset_days
        base_date_field = TaskService.map_base_date_to_field(task_type_doc.base_date, task_type_doc.triggering_doctype)
        print(f"DEBUG: base_date_field for Task {task_name} is {base_date_field}")
        if offset_direction == "Before":
            offset_days = -offset_days
        
        base_date_field_value = triggering_doc_dict.get(base_date_field)
        print(f"DEBUG: base_date_field_value for Task {task_name} is {base_date_field_value}")
        
        if isinstance(base_date_field_value, datetime):
            base_date_field_value = base_date_field_value
        else:
            base_date_field_value = getdate(base_date_field_value)
        
        calculated_due_date = base_date_field_value + timedelta(days=offset_days) 
        calculated_start_date = None
        if task_type_doc.days > 1:
            calculated_start_date = calculated_due_date - timedelta(days=task_type_doc.days)
        print(f"DEBUG: Calculated dates for Task {task_name} - Due Date: {calculated_due_date}, Start Date: {calculated_start_date}")
        return calculated_due_date, calculated_start_date
        
    @staticmethod
    def determine_due_date_update_required(task_name: str, calculated_due_date: date | None, calculated_start_date: date | None) -> bool:
        current_due_date = frappe.get_value("Task", task_name, "exp_end_date")
        current_start_date = frappe.get_value("Task", task_name, "exp_start_date")
        if current_due_date != calculated_due_date or current_start_date != calculated_start_date:
            print(f"DEBUG: Update required for Task {task_name}. Current due date: {current_due_date}, Calculated due date: {calculated_due_date}. Current start date: {current_start_date}, Calculated start date: {calculated_start_date}")
            return True
        else:
            print(f"DEBUG: No update required for Task {task_name}. Dates are up to date.")
            return False
         
    @staticmethod
    def determine_completion_update_required(task_name: str, task_type_doc) -> bool:
        current_status = frappe.get_value("Task", task_name, "status")
        determination = False
        if current_status == "Completed":
            print(f"DEBUG: Task {task_name} is already marked as Completed, no update required.")
            return False
        else:
            triggering_doc_dict = TaskService.get_triggering_doc_dict(task_name=task_name, doctype=task_type_doc.custom_completion_trigger_doctype)
            check_field = TaskService.map_completion_check_field(task_type_doc.custom_completion_trigger)
            check_value = triggering_doc_dict.get(check_field)
            trigger = task_type_doc.custom_completion_trigger
            if trigger == "Completed" and check_value == "Completed" and current_status != "Completed":
                determination = True
            elif trigger == "Percentage Reached" and check_value >= task_type_doc.custom_target_percent and current_status != "Completed":
                determination = True
            elif trigger in ["Scheduled", "Started", "Created"] and check_value and current_status != "Completed":
                determination = True
            print(f"DEBUG: Completion trigger '{trigger}' met for Task {task_name}, check field {check_field} has value {check_value}.")
            return determination
            
            
            
    @staticmethod
    def get_triggering_doc_dict(task_name: str, doctype, task_type_calculate_from = None) -> dict | None:
        project_name = frappe.get_value("Task", task_name, "project")
        print(f"DEBUG: Project name: {project_name}")
        dict = None
        if doctype == "Project":
            dict = frappe.get_doc("Project", project_name).as_dict()
        if doctype == "Service Address 2":
            service_name = frappe.get_value("Project", project_name, "service_appointment")
            dict = frappe.get_doc("Service Address 2", service_name).as_dict()
        if doctype == "Task":
            project_doc = frappe.get_doc("Project", project_name)
            for task in project_doc.tasks:
                if task.task_type == task_type_calculate_from:
                    dict = frappe.get_doc("Task", task.task).as_dict()
        print(f"DEBUG: Triggering doc dict for Task {task_name}: {dict}")
        return dict
                
    @staticmethod
    def update_task(task_name: str, calculated_due_date: date | None = None, calculated_start_date: date | None = None, status: str | None = None):
        task_doc = frappe.get_doc("Task", task_name)
        if calculated_due_date is not None:
            task_doc.exp_end_date = calculated_due_date
        if calculated_start_date is not None:
            task_doc.exp_start_date = calculated_start_date
        if status is not None:
            task_doc.status = status
            if status == "Completed":
                task_doc.actual_end_date = datetime.now()
        task_doc.save(ignore_permissions=True)
        print(f"DEBUG: Updated Task {task_name} with new dates - Start: {calculated_start_date}, End: {calculated_due_date}, Status: {status}")
    
    @staticmethod
    def map_base_date_to_field(base_date: str, triggering_doctype: str) -> str:
        """Map a base date configuration to a corresponding field name."""
        base_date_field_map = {
            "Start": "expected_start_date",
            "End": "expected_end_date",
            "Creation": "creation",
            "Completion": "actual_end_date"
        }
        task_date_field_map = {
            "Start": "exp_start_date",
            "End": "exp_end_date",
            "Creation": "creation",
            "Completion": "actual_end_date"
        }
        if triggering_doctype == "Task":
            return task_date_field_map.get(base_date, "exp_end_date")
        return base_date_field_map.get(base_date, "expected_end_date")
        
    @staticmethod
    def map_completion_check_field(completion_trigger: str) -> str:
        completion_check_field_map = {
            "Completed": "status",
            "Scheduled": "expected_end_date",
            "Created": "creation",
            "Started": "actual_start_date",
            "Percentage Reached": "progress"
        }
        return completion_check_field_map.get(completion_trigger, "status")
        
    @staticmethod
    def determine_event(triggering_doc) -> str | None:
        print("DEBUG: Current Document:", triggering_doc.as_dict())
        if not frappe.db.exists(triggering_doc.doctype, triggering_doc.name):
            print("DEBUG: Document does not exist in database, returning None for event.")
            return None
        prev_doc = frappe.get_doc(triggering_doc.doctype, triggering_doc.name, as_dict=False, ignore_if_missing=True)
        start_date_field = "expected_start_date" if triggering_doc.doctype != "Task" else "exp_start_date"
        end_date_field = "expected_end_date" if triggering_doc.doctype != "Task" else "exp_end_date"
        print("DEBUG: Previous Document:", prev_doc.as_dict() if prev_doc else "None")
        if not prev_doc:
            return None
        if getattr(prev_doc, end_date_field) != getattr(triggering_doc, end_date_field) or getattr(prev_doc, start_date_field) != getattr(triggering_doc, start_date_field):
            return "Scheduled"
        elif prev_doc.status != triggering_doc.status and triggering_doc.status == "Completed":
            return "Completed"
        else:
            return None
        
    @staticmethod
    def find_and_update_overdue_tasks():
        today = date.today()
        overdue_tasks = frappe.get_all("Task", filters={"exp_end_date": ("<", today), "status": ["not in", ["Completed", "Template", "Cancelled", "Overdue"]]}, pluck="name")
        print(f"DEBUG: Found {len(overdue_tasks)} overdue tasks.")
        for task_name in overdue_tasks:
            task_doc = frappe.get_doc("Task", task_name)
            task_doc.status = "Overdue"
            task_doc.save(ignore_permissions=True)
            print(f"DEBUG: Updated Task {task_name} to Overdue status.")
        frappe.db.commit()
        