"""
PM Module — Task & Milestone Entities

تسک، مایلستون و وابستگی — هسته زمان‌بندی پروژه.
"""

from dataclasses import dataclass, field
from datetime import date
from decimal import Decimal
from typing import Optional, List
from uuid import UUID, uuid4

from shared.base_classes.entity import TenantEntity

from ..value_objects.common import (
    TaskStatus,
    TaskType,
    DependencyType,
    AssignmentPolicy,
    TaskConstraintType,
    SchedulingMode,
    DurationType,
    PercentCompleteType,
    TaskAssignmentRole,
)


@dataclass
class Task(TenantEntity):
    """
    تسک پروژه — واحد اصلی زمان‌بندی.

    ساختار درختی: parent_id → self (WBS hierarchy)
    Summary Task: تسکی که فرزند دارد — تاریخ‌ها و پیشرفت از فرزندان محاسبه می‌شود.
    """

    # --- Identity ---
    project_id: UUID = field(default_factory=uuid4)
    code: str = ""               # WBS code: 1.2.3
    title: str = ""
    description: str = ""

    # --- Hierarchy ---
    parent_id: Optional[UUID] = None  # Summary task parent
    sort_order: int = 0               # ترتیب نمایش در لیست

    # --- Type & Status ---
    task_type: TaskType = TaskType.TASK
    status: TaskStatus = TaskStatus.NOT_STARTED

    # --- Dates ---
    planned_start: Optional[date] = None
    planned_end: Optional[date] = None
    actual_start: Optional[date] = None
    actual_end: Optional[date] = None
    duration: int = 1            # روز کاری
    remaining_duration: Optional[int] = None  # روز کاری باقیمانده

    # --- Progress ---
    progress: int = 0            # 0-100 (physical)
    percent_complete_type: PercentCompleteType = PercentCompleteType.PHYSICAL

    # --- Cost ---
    planned_cost: Decimal = Decimal("0")
    actual_cost: Decimal = Decimal("0")

    # --- Constraints ---
    is_critical: bool = False    # آیا در مسیر بحرانی است
    is_milestone: bool = False
    constraint_type: TaskConstraintType = TaskConstraintType.ASAP
    constraint_date: Optional[date] = None
    deadline: Optional[date] = None  # ددلاین (warning, not hard constraint)

    # --- Scheduling ---
    scheduling_mode: SchedulingMode = SchedulingMode.AUTO
    duration_type: DurationType = DurationType.FIXED_DURATION
    early_start: Optional[date] = None   # ES — CPM
    early_finish: Optional[date] = None  # EF
    late_start: Optional[date] = None    # LS
    late_finish: Optional[date] = None   # LF
    total_float: Optional[int] = None    # شناوری کل
    free_float: Optional[int] = None     # شناوری آزاد

    # --- Calendar ---
    calendar_id: Optional[UUID] = None   # تقویم اختصاصی تسک

    # --- Assignment ---
    assignee_id: Optional[UUID] = None   # → User (backward compat — primary assignee)
    assignment_policy: AssignmentPolicy = AssignmentPolicy.MANUAL

    # --- Effort ---
    is_effort_driven: bool = False  # آیا تغییر منابع مدت را تغییر می‌دهد

    # --- Metadata ---
    color: str = ""
    tags: Optional[list] = field(default_factory=list)
    notes: str = ""
    metadata: Optional[dict] = field(default_factory=dict)

    # ─── Business Rules ───

    def mark_ready(self) -> None:
        """علامت‌گذاری تسک به‌عنوان آماده."""
        if self.status != TaskStatus.NOT_STARTED:
            raise ValueError("فقط تسک‌های شروع‌نشده قابل آماده‌سازی هستند")
        self.status = TaskStatus.READY

    def start_work(self) -> None:
        if self.status not in (TaskStatus.NOT_STARTED, TaskStatus.READY):
            raise ValueError("تسک قبلاً شروع شده")
        self.status = TaskStatus.IN_PROGRESS
        self.actual_start = date.today()

    def hold(self) -> None:
        """توقف تسک."""
        if self.status != TaskStatus.IN_PROGRESS:
            raise ValueError("فقط تسک‌های در حال انجام قابل توقف هستند")
        self.status = TaskStatus.ON_HOLD

    def resume(self) -> None:
        """ازسرگیری تسک متوقف."""
        if self.status != TaskStatus.ON_HOLD:
            raise ValueError("فقط تسک‌های متوقف قابل ازسرگیری هستند")
        self.status = TaskStatus.IN_PROGRESS

    def cancel(self) -> None:
        """لغو تسک."""
        if self.status in (TaskStatus.COMPLETED, TaskStatus.CANCELLED):
            raise ValueError("تسک تکمیل‌شده یا لغوشده قابل لغو نیست")
        self.status = TaskStatus.CANCELLED

    def complete(self) -> None:
        if self.status not in (TaskStatus.NOT_STARTED, TaskStatus.READY, TaskStatus.IN_PROGRESS):
            raise ValueError("این تسک قابل تکمیل نیست")
        self.status = TaskStatus.COMPLETED
        self.actual_end = date.today()
        self.progress = 100

    def update_progress(self, progress: int) -> None:
        if not (0 <= progress <= 100):
            raise ValueError("پیشرفت باید بین ۰ تا ۱۰۰ باشد")
        self.progress = progress
        if progress == 100 and self.status != TaskStatus.COMPLETED:
            self.complete()
        elif progress > 0 and self.status == TaskStatus.NOT_STARTED:
            self.start_work()

    @property
    def is_overdue(self) -> bool:
        if self.planned_end and self.status in (
            TaskStatus.NOT_STARTED, TaskStatus.READY, TaskStatus.IN_PROGRESS,
        ):
            return date.today() > self.planned_end
        return False

    @property
    def cost_variance(self) -> Decimal:
        return self.planned_cost - self.actual_cost


@dataclass
class Dependency(TenantEntity):
    """
    وابستگی بین تسک‌ها.
    FS, SS, FF, SF با lead/lag
    """

    project_id: UUID = field(default_factory=uuid4)
    predecessor_id: UUID = field(default_factory=uuid4)
    successor_id: UUID = field(default_factory=uuid4)
    dependency_type: DependencyType = DependencyType.FS
    lag_days: int = 0   # مثبت = تأخیر، منفی = lead

    def validate(self) -> None:
        if self.predecessor_id == self.successor_id:
            raise ValueError("تسک نمی‌تواند به خودش وابسته باشد")


@dataclass
class TaskAssignment(TenantEntity):
    """
    تخصیص کاربر/نقش به تسک — سیستم Hybrid Assignment.

    یک تسک می‌تواند چند تخصیص‌یافته داشته باشد (primary, secondary, reviewer, observer).
    """

    task_id: UUID = field(default_factory=uuid4)
    user_id: Optional[UUID] = None     # → User
    role_id: Optional[UUID] = None     # → Role (optional)
    assignment_role: TaskAssignmentRole = TaskAssignmentRole.PRIMARY
    is_active: bool = True
    notes: str = ""

    def deactivate(self) -> None:
        self.is_active = False
