"""
PM Repository Implementations — Project & Task.

پیاده‌سازی repositoryهای پروژه و تسک با Django ORM.
"""
from datetime import date
from typing import Optional, List
from uuid import UUID

from django.db.models import Q, Sum, Avg, Count, F, Max

from ...domain.entities.project import Project
from ...domain.entities.task import Task, Dependency
from ...domain.repositories.interfaces import (
    IProjectRepository,
    ITaskRepository,
    IDependencyRepository,
)
from ..persistence.models import ProjectModel, TaskModel, DependencyModel
from .mappers import ProjectMapper, TaskMapper, DependencyMapper


class DjangoProjectRepository(IProjectRepository):
    """پیاده‌سازی مخزن پروژه با Django ORM."""

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: Project) -> Project:
        try:
            model = ProjectModel.objects.get(id=entity.id)
            model = ProjectMapper.to_model(entity, model)
        except ProjectModel.DoesNotExist:
            model = ProjectMapper.to_model(entity)
        model.save()
        return ProjectMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[Project]:
        try:
            model = ProjectModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ProjectMapper.to_entity(model)
        except ProjectModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[Project]:
        qs = ProjectModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ProjectMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ProjectModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_code(self, code: str) -> Optional[Project]:
        try:
            model = ProjectModel.objects.get(
                code=code, tenant_id=self._tenant_id
            )
            return ProjectMapper.to_entity(model)
        except ProjectModel.DoesNotExist:
            return None

    def find_by_status(self, status: str) -> List[Project]:
        qs = ProjectModel.objects.filter(
            tenant_id=self._tenant_id, status=status
        )
        return [ProjectMapper.to_entity(m) for m in qs]

    def find_by_manager(self, manager_id: UUID) -> List[Project]:
        qs = ProjectModel.objects.filter(
            tenant_id=self._tenant_id, manager_id=manager_id
        )
        return [ProjectMapper.to_entity(m) for m in qs]

    def find_by_company(self, company_id: UUID) -> List[Project]:
        qs = ProjectModel.objects.filter(
            tenant_id=self._tenant_id, company_id=company_id
        )
        return [ProjectMapper.to_entity(m) for m in qs]

    def find_children(self, parent_id: UUID) -> List[Project]:
        qs = ProjectModel.objects.filter(
            tenant_id=self._tenant_id, parent_id=parent_id
        )
        return [ProjectMapper.to_entity(m) for m in qs]

    def find_portfolio_tree(self, tenant_id: UUID = None) -> List[Project]:
        """ساختار درختی پورتفولیو — ابتدا والدها سپس فرزندان."""
        tid = tenant_id or self._tenant_id
        qs = ProjectModel.objects.filter(
            tenant_id=tid, parent__isnull=True
        ).order_by("code")
        return [ProjectMapper.to_entity(m) for m in qs]

    def find_active(self) -> List[Project]:
        qs = ProjectModel.objects.filter(
            tenant_id=self._tenant_id, status="active"
        )
        return [ProjectMapper.to_entity(m) for m in qs]

    def count_by_status(self) -> dict:
        qs = ProjectModel.objects.filter(
            tenant_id=self._tenant_id
        ).values("status").annotate(count=Count("id"))
        return {item["status"]: item["count"] for item in qs}


class DjangoTaskRepository(ITaskRepository):
    """پیاده‌سازی مخزن تسک با Django ORM."""

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: Task) -> Task:
        try:
            model = TaskModel.objects.get(id=entity.id)
            model = TaskMapper.to_model(entity, model)
        except TaskModel.DoesNotExist:
            model = TaskMapper.to_model(entity)
        model.save()
        return TaskMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[Task]:
        try:
            model = TaskModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskMapper.to_entity(model)
        except TaskModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[Task]:
        qs = TaskModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID) -> List[Task]:
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id
        ).order_by("sort_order", "created_at")
        return [TaskMapper.to_entity(m) for m in qs]

    def find_root_tasks(self, project_id: UUID) -> List[Task]:
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id,
            project_id=project_id,
            parent__isnull=True,
        ).order_by("sort_order")
        return [TaskMapper.to_entity(m) for m in qs]

    def find_children(self, parent_id: UUID) -> List[Task]:
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id, parent_id=parent_id
        ).order_by("sort_order")
        return [TaskMapper.to_entity(m) for m in qs]

    def find_critical_path(self, project_id: UUID) -> List[Task]:
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id,
            project_id=project_id,
            is_critical=True,
        ).order_by("early_start")
        return [TaskMapper.to_entity(m) for m in qs]

    def find_milestones(self, project_id: UUID) -> List[Task]:
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id,
            project_id=project_id,
            task_type="milestone",
        ).order_by("planned_start")
        return [TaskMapper.to_entity(m) for m in qs]

    def find_by_assignee(self, assignee_id: UUID) -> List[Task]:
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id, assignee_id=assignee_id
        )
        return [TaskMapper.to_entity(m) for m in qs]

    def find_overdue(self, project_id: UUID) -> List[Task]:
        today = date.today()
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id,
            project_id=project_id,
            planned_end__lt=today,
        ).exclude(status__in=["completed", "cancelled"])
        return [TaskMapper.to_entity(m) for m in qs]

    def get_max_sort_order(self, project_id: UUID, parent_id: Optional[UUID] = None) -> int:
        qs = TaskModel.objects.filter(
            tenant_id=self._tenant_id,
            project_id=project_id,
        )
        if parent_id:
            qs = qs.filter(parent_id=parent_id)
        else:
            qs = qs.filter(parent__isnull=True)
        result = qs.aggregate(max_order=Max("sort_order"))
        return result.get("max_order") or 0

    def bulk_update_cpm(self, tasks: List[Task]) -> None:
        """بروزرسانی فیلدهای CPM تسک‌ها (batch)."""
        for task in tasks:
            TaskModel.objects.filter(id=task.id).update(
                early_start=task.early_start,
                early_finish=task.early_finish,
                late_start=task.late_start,
                late_finish=task.late_finish,
                total_float=task.total_float,
                free_float=task.free_float,
                is_critical=task.is_critical,
            )

    def bulk_update(self, tasks: List[Task]) -> List[Task]:
        """بروزرسانی دسته‌ای تسک‌ها."""
        results = []
        for task in tasks:
            saved = self.save(task)
            results.append(saved)
        return results


class DjangoDependencyRepository(IDependencyRepository):
    """پیاده‌سازی مخزن وابستگی با Django ORM."""

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: Dependency) -> Dependency:
        try:
            model = DependencyModel.objects.get(id=entity.id)
            model = DependencyMapper.to_model(entity, model)
        except DependencyModel.DoesNotExist:
            model = DependencyMapper.to_model(entity)
        model.save()
        return DependencyMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[Dependency]:
        try:
            model = DependencyModel.objects.get(id=id, tenant_id=self._tenant_id)
            return DependencyMapper.to_entity(model)
        except DependencyModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[Dependency]:
        qs = DependencyModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [DependencyMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = DependencyModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID) -> List[Dependency]:
        qs = DependencyModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id
        )
        return [DependencyMapper.to_entity(m) for m in qs]

    def find_predecessors(self, task_id: UUID) -> List[Dependency]:
        qs = DependencyModel.objects.filter(
            tenant_id=self._tenant_id, successor_id=task_id
        )
        return [DependencyMapper.to_entity(m) for m in qs]

    def find_successors(self, task_id: UUID) -> List[Dependency]:
        qs = DependencyModel.objects.filter(
            tenant_id=self._tenant_id, predecessor_id=task_id
        )
        return [DependencyMapper.to_entity(m) for m in qs]

    def exists(self, predecessor_id: UUID, successor_id: UUID) -> bool:
        return DependencyModel.objects.filter(
            tenant_id=self._tenant_id,
            predecessor_id=predecessor_id,
            successor_id=successor_id,
        ).exists()

    def delete_by_task(self, task_id: UUID) -> int:
        deleted, _ = DependencyModel.objects.filter(
            tenant_id=self._tenant_id,
        ).filter(
            Q(predecessor_id=task_id) | Q(successor_id=task_id)
        ).delete()
        return deleted
