"""
PM Repository Implementations — Resource & Cost.

پیاده‌سازی repositoryهای منبع و هزینه با Django ORM.
"""
from datetime import date
from typing import Optional, List
from uuid import UUID

from django.db.models import Sum, Q

from ...domain.entities.resource import Resource, ResourceAssignment
from ...domain.entities.cost import Budget, CostEntry
from ...domain.repositories.interfaces import (
    IResourceRepository,
    IResourceAssignmentRepository,
    IBudgetRepository,
    ICostEntryRepository,
)
from ..persistence.models import (
    ResourceModel,
    ResourceAssignmentModel,
    BudgetModel,
    CostEntryModel,
)
from .mappers import (
    ResourceMapper,
    ResourceAssignmentMapper,
    BudgetMapper,
    CostEntryMapper,
)


class DjangoResourceRepository(IResourceRepository):
    """پیاده‌سازی مخزن منبع با Django ORM."""

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: Resource) -> Resource:
        try:
            model = ResourceModel.objects.get(id=entity.id)
            model = ResourceMapper.to_model(entity, model)
        except ResourceModel.DoesNotExist:
            model = ResourceMapper.to_model(entity)
        model.save()
        return ResourceMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[Resource]:
        try:
            model = ResourceModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ResourceMapper.to_entity(model)
        except ResourceModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[Resource]:
        qs = ResourceModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ResourceMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ResourceModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_type(self, resource_type: str) -> List[Resource]:
        qs = ResourceModel.objects.filter(
            tenant_id=self._tenant_id, resource_type=resource_type, is_active=True
        )
        return [ResourceMapper.to_entity(m) for m in qs]

    def find_by_employee(self, employee_id: UUID) -> Optional[Resource]:
        try:
            model = ResourceModel.objects.get(
                tenant_id=self._tenant_id, employee_id=employee_id
            )
            return ResourceMapper.to_entity(model)
        except ResourceModel.DoesNotExist:
            return None

    def find_by_code(self, code: str) -> Optional[Resource]:
        try:
            model = ResourceModel.objects.get(
                tenant_id=self._tenant_id, code=code
            )
            return ResourceMapper.to_entity(model)
        except ResourceModel.DoesNotExist:
            return None

    def find_active(self) -> List[Resource]:
        qs = ResourceModel.objects.filter(
            tenant_id=self._tenant_id, is_active=True
        )
        return [ResourceMapper.to_entity(m) for m in qs]


class DjangoResourceAssignmentRepository(IResourceAssignmentRepository):
    """پیاده‌سازی مخزن تخصیص منبع با Django ORM."""

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ResourceAssignment) -> ResourceAssignment:
        try:
            model = ResourceAssignmentModel.objects.get(id=entity.id)
            model = ResourceAssignmentMapper.to_model(entity, model)
        except ResourceAssignmentModel.DoesNotExist:
            model = ResourceAssignmentMapper.to_model(entity)
        model.save()
        return ResourceAssignmentMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ResourceAssignment]:
        try:
            model = ResourceAssignmentModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ResourceAssignmentMapper.to_entity(model)
        except ResourceAssignmentModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ResourceAssignment]:
        qs = ResourceAssignmentModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ResourceAssignmentMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ResourceAssignmentModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_task(self, task_id: UUID) -> List[ResourceAssignment]:
        qs = ResourceAssignmentModel.objects.filter(
            tenant_id=self._tenant_id, task_id=task_id, is_active=True
        )
        return [ResourceAssignmentMapper.to_entity(m) for m in qs]

    def find_by_resource(self, resource_id: UUID) -> List[ResourceAssignment]:
        qs = ResourceAssignmentModel.objects.filter(
            tenant_id=self._tenant_id, resource_id=resource_id, is_active=True
        )
        return [ResourceAssignmentMapper.to_entity(m) for m in qs]

    def find_by_project(self, project_id: UUID) -> List[ResourceAssignment]:
        qs = ResourceAssignmentModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id
        )
        return [ResourceAssignmentMapper.to_entity(m) for m in qs]

    def get_resource_utilization(self, resource_id: UUID, start: date, end: date) -> dict:
        qs = ResourceAssignmentModel.objects.filter(
            tenant_id=self._tenant_id,
            resource_id=resource_id,
            is_active=True,
            start_date__lte=end,
            end_date__gte=start,
        )
        result = qs.aggregate(
            total_units=Sum("units"),
            total_planned_hours=Sum("planned_hours"),
            total_actual_hours=Sum("actual_hours"),
        )
        return {
            "total_units": result["total_units"] or 0,
            "total_planned_hours": result["total_planned_hours"] or 0,
            "total_actual_hours": result["total_actual_hours"] or 0,
        }


class DjangoBudgetRepository(IBudgetRepository):
    """پیاده‌سازی مخزن بودجه با Django ORM."""

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: Budget) -> Budget:
        try:
            model = BudgetModel.objects.get(id=entity.id)
            model = BudgetMapper.to_model(entity, model)
        except BudgetModel.DoesNotExist:
            model = BudgetMapper.to_model(entity)
        model.save()
        return BudgetMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[Budget]:
        try:
            model = BudgetModel.objects.get(id=id, tenant_id=self._tenant_id)
            return BudgetMapper.to_entity(model)
        except BudgetModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[Budget]:
        qs = BudgetModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [BudgetMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = BudgetModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID) -> List[Budget]:
        qs = BudgetModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id
        )
        return [BudgetMapper.to_entity(m) for m in qs]

    def find_by_task(self, task_id: UUID) -> Optional[Budget]:
        try:
            model = BudgetModel.objects.get(
                tenant_id=self._tenant_id, task_id=task_id, is_active=True
            )
            return BudgetMapper.to_entity(model)
        except BudgetModel.DoesNotExist:
            return None

    def get_project_budget_summary(self, project_id: UUID) -> dict:
        qs = BudgetModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id, is_active=True
        )
        result = qs.aggregate(
            total_original=Sum("original_budget"),
            total_revised=Sum("revised_budget"),
            total_committed=Sum("committed_cost"),
            total_actual=Sum("actual_cost"),
        )
        return {
            "total_original": result["total_original"] or 0,
            "total_revised": result["total_revised"] or 0,
            "total_committed": result["total_committed"] or 0,
            "total_actual": result["total_actual"] or 0,
        }


class DjangoCostEntryRepository(ICostEntryRepository):
    """پیاده‌سازی مخزن ثبت هزینه با Django ORM."""

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: CostEntry) -> CostEntry:
        try:
            model = CostEntryModel.objects.get(id=entity.id)
            model = CostEntryMapper.to_model(entity, model)
        except CostEntryModel.DoesNotExist:
            model = CostEntryMapper.to_model(entity)
        model.save()
        return CostEntryMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[CostEntry]:
        try:
            model = CostEntryModel.objects.get(id=id, tenant_id=self._tenant_id)
            return CostEntryMapper.to_entity(model)
        except CostEntryModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[CostEntry]:
        qs = CostEntryModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [CostEntryMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = CostEntryModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID) -> List[CostEntry]:
        qs = CostEntryModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id
        ).order_by("-entry_date")
        return [CostEntryMapper.to_entity(m) for m in qs]

    def find_by_task(self, task_id: UUID) -> List[CostEntry]:
        qs = CostEntryModel.objects.filter(
            tenant_id=self._tenant_id, task_id=task_id
        ).order_by("-entry_date")
        return [CostEntryMapper.to_entity(m) for m in qs]

    def get_total_by_project(self, project_id: UUID) -> dict:
        qs = CostEntryModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id
        )
        result = qs.aggregate(total=Sum("amount"))
        return {"total": result["total"] or 0}

    def get_costs_by_type(self, project_id: UUID) -> dict:
        qs = CostEntryModel.objects.filter(
            tenant_id=self._tenant_id, project_id=project_id
        ).values("cost_type").annotate(total=Sum("amount"))
        return {item["cost_type"]: item["total"] for item in qs}

    def find_by_date_range(self, project_id: UUID, start: date, end: date) -> List[CostEntry]:
        qs = CostEntryModel.objects.filter(
            tenant_id=self._tenant_id,
            project_id=project_id,
            entry_date__gte=start,
            entry_date__lte=end,
        ).order_by("entry_date")
        return [CostEntryMapper.to_entity(m) for m in qs]
