"""
PM Repository Implementations — Advanced Entities.

پیاده‌سازی repositoryهای پیشرفته: Activity Code, UDF, Resource Skills,
CBS (Cost Accounts), Funding, CashFlow, Schedule Health, Project Template.
"""
from datetime import date
from typing import Optional, List
from uuid import UUID

from django.db.models import Q, Max

from ...domain.entities.task import TaskAssignment
from ...domain.entities.kanban import (
    TaskLabel, TaskLabelAssignment,
    TaskAttachment, TaskChecklist, TaskChecklistItem,
    RACIAssignment,
)
from ...domain.entities.advanced import (
    ActivityCodeType, ActivityCodeValue, TaskActivityCode,
    UDFDefinition, UDFValue,
    ResourceSkill, ResourceAvailability, ResourceCostRate,
    CostAccount, FundingSource, CashFlowEntry,
    ScheduleHealthSnapshot, ProjectTemplate,
)
from ...domain.repositories.interfaces import (
    ITaskAssignmentRepository,
    ITaskLabelRepository, ITaskLabelAssignmentRepository,
    ITaskAttachmentRepository, ITaskChecklistRepository,
    ITaskChecklistItemRepository, IRACIAssignmentRepository,
    IActivityCodeTypeRepository, IActivityCodeValueRepository,
    ITaskActivityCodeRepository,
    IUDFDefinitionRepository, IUDFValueRepository,
    IResourceSkillRepository, IResourceAvailabilityRepository,
    IResourceCostRateRepository,
    ICostAccountRepository, IFundingSourceRepository,
    ICashFlowEntryRepository,
    IScheduleHealthSnapshotRepository, IProjectTemplateRepository,
)
from ..persistence.models import (
    TaskAssignmentModel,
    TaskLabelModel, TaskLabelAssignmentModel,
    TaskAttachmentModel, TaskChecklistModel, TaskChecklistItemModel,
    RACIAssignmentModel,
    ActivityCodeTypeModel, ActivityCodeValueModel, TaskActivityCodeModel,
    UDFDefinitionModel, UDFValueModel,
    ResourceSkillModel, ResourceAvailabilityModel, ResourceCostRateModel,
    CostAccountModel, FundingSourceModel, CashFlowEntryModel,
    ScheduleHealthSnapshotModel, ProjectTemplateModel,
)
from .mappers import (
    TaskAssignmentMapper,
    TaskLabelMapper, TaskLabelAssignmentMapper,
    TaskAttachmentMapper, TaskChecklistMapper, TaskChecklistItemMapper,
    RACIAssignmentMapper,
    ActivityCodeTypeMapper, ActivityCodeValueMapper, TaskActivityCodeMapper,
    UDFDefinitionMapper, UDFValueMapper,
    ResourceSkillMapper, ResourceAvailabilityMapper, ResourceCostRateMapper,
    CostAccountMapper, FundingSourceMapper, CashFlowEntryMapper,
    ScheduleHealthSnapshotMapper, ProjectTemplateMapper,
)


# ═══════════════════════════════════════════════════
# Task Assignment Repository
# ═══════════════════════════════════════════════════

class DjangoTaskAssignmentRepository(ITaskAssignmentRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: TaskAssignment) -> TaskAssignment:
        try:
            model = TaskAssignmentModel.objects.get(id=entity.id)
            model = TaskAssignmentMapper.to_model(entity, model)
        except TaskAssignmentModel.DoesNotExist:
            model = TaskAssignmentMapper.to_model(entity)
        model.save()
        return TaskAssignmentMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[TaskAssignment]:
        try:
            model = TaskAssignmentModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskAssignmentMapper.to_entity(model)
        except TaskAssignmentModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[TaskAssignment]:
        qs = TaskAssignmentModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskAssignmentMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskAssignmentModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_task(self, task_id: UUID, tenant_id: UUID = None) -> List[TaskAssignment]:
        qs = TaskAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, task_id=task_id
        )
        return [TaskAssignmentMapper.to_entity(m) for m in qs]

    def find_by_user(self, user_id: UUID, tenant_id: UUID = None) -> List[TaskAssignment]:
        qs = TaskAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, user_id=user_id
        )
        return [TaskAssignmentMapper.to_entity(m) for m in qs]

    def find_active_by_task(self, task_id: UUID, tenant_id: UUID = None) -> List[TaskAssignment]:
        qs = TaskAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            task_id=task_id, is_active=True,
        )
        return [TaskAssignmentMapper.to_entity(m) for m in qs]

    def find_primary_assignee(self, task_id: UUID, tenant_id: UUID = None) -> Optional[TaskAssignment]:
        try:
            model = TaskAssignmentModel.objects.get(
                tenant_id=tenant_id or self._tenant_id,
                task_id=task_id,
                assignment_role="primary",
                is_active=True,
            )
            return TaskAssignmentMapper.to_entity(model)
        except TaskAssignmentModel.DoesNotExist:
            return None


# ═══════════════════════════════════════════════════
# Kanban Repositories
# ═══════════════════════════════════════════════════

class DjangoTaskLabelRepository(ITaskLabelRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: TaskLabel) -> TaskLabel:
        try:
            model = TaskLabelModel.objects.get(id=entity.id)
            model = TaskLabelMapper.to_model(entity, model)
        except TaskLabelModel.DoesNotExist:
            model = TaskLabelMapper.to_model(entity)
        model.save()
        return TaskLabelMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[TaskLabel]:
        try:
            model = TaskLabelModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskLabelMapper.to_entity(model)
        except TaskLabelModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[TaskLabel]:
        qs = TaskLabelModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskLabelMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskLabelModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID, tenant_id: UUID = None) -> List[TaskLabel]:
        qs = TaskLabelModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, project_id=project_id
        ).order_by("sort_order")
        return [TaskLabelMapper.to_entity(m) for m in qs]


class DjangoTaskLabelAssignmentRepository(ITaskLabelAssignmentRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: TaskLabelAssignment) -> TaskLabelAssignment:
        try:
            model = TaskLabelAssignmentModel.objects.get(id=entity.id)
            model = TaskLabelAssignmentMapper.to_model(entity, model)
        except TaskLabelAssignmentModel.DoesNotExist:
            model = TaskLabelAssignmentMapper.to_model(entity)
        model.save()
        return TaskLabelAssignmentMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[TaskLabelAssignment]:
        try:
            model = TaskLabelAssignmentModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskLabelAssignmentMapper.to_entity(model)
        except TaskLabelAssignmentModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[TaskLabelAssignment]:
        qs = TaskLabelAssignmentModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskLabelAssignmentMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskLabelAssignmentModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_task(self, task_id: UUID, tenant_id: UUID = None) -> List[TaskLabelAssignment]:
        qs = TaskLabelAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, task_id=task_id
        )
        return [TaskLabelAssignmentMapper.to_entity(m) for m in qs]

    def find_by_label(self, label_id: UUID, tenant_id: UUID = None) -> List[TaskLabelAssignment]:
        qs = TaskLabelAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, label_id=label_id
        )
        return [TaskLabelAssignmentMapper.to_entity(m) for m in qs]


class DjangoTaskAttachmentRepository(ITaskAttachmentRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: TaskAttachment) -> TaskAttachment:
        try:
            model = TaskAttachmentModel.objects.get(id=entity.id)
            model = TaskAttachmentMapper.to_model(entity, model)
        except TaskAttachmentModel.DoesNotExist:
            model = TaskAttachmentMapper.to_model(entity)
        model.save()
        return TaskAttachmentMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[TaskAttachment]:
        try:
            model = TaskAttachmentModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskAttachmentMapper.to_entity(model)
        except TaskAttachmentModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[TaskAttachment]:
        qs = TaskAttachmentModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskAttachmentMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskAttachmentModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_task(self, task_id: UUID, tenant_id: UUID = None) -> List[TaskAttachment]:
        qs = TaskAttachmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, task_id=task_id
        )
        return [TaskAttachmentMapper.to_entity(m) for m in qs]


class DjangoTaskChecklistRepository(ITaskChecklistRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: TaskChecklist) -> TaskChecklist:
        try:
            model = TaskChecklistModel.objects.get(id=entity.id)
            model = TaskChecklistMapper.to_model(entity, model)
        except TaskChecklistModel.DoesNotExist:
            model = TaskChecklistMapper.to_model(entity)
        model.save()
        return TaskChecklistMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[TaskChecklist]:
        try:
            model = TaskChecklistModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskChecklistMapper.to_entity(model)
        except TaskChecklistModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[TaskChecklist]:
        qs = TaskChecklistModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskChecklistMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskChecklistModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_task(self, task_id: UUID, tenant_id: UUID = None) -> List[TaskChecklist]:
        qs = TaskChecklistModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, task_id=task_id
        ).order_by("sort_order")
        return [TaskChecklistMapper.to_entity(m) for m in qs]


class DjangoTaskChecklistItemRepository(ITaskChecklistItemRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: TaskChecklistItem) -> TaskChecklistItem:
        try:
            model = TaskChecklistItemModel.objects.get(id=entity.id)
            model = TaskChecklistItemMapper.to_model(entity, model)
        except TaskChecklistItemModel.DoesNotExist:
            model = TaskChecklistItemMapper.to_model(entity)
        model.save()
        return TaskChecklistItemMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[TaskChecklistItem]:
        try:
            model = TaskChecklistItemModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskChecklistItemMapper.to_entity(model)
        except TaskChecklistItemModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[TaskChecklistItem]:
        qs = TaskChecklistItemModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskChecklistItemMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskChecklistItemModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_checklist(self, checklist_id: UUID, tenant_id: UUID = None) -> List[TaskChecklistItem]:
        qs = TaskChecklistItemModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, checklist_id=checklist_id
        ).order_by("sort_order")
        return [TaskChecklistItemMapper.to_entity(m) for m in qs]


class DjangoRACIAssignmentRepository(IRACIAssignmentRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: RACIAssignment) -> RACIAssignment:
        try:
            model = RACIAssignmentModel.objects.get(id=entity.id)
            model = RACIAssignmentMapper.to_model(entity, model)
        except RACIAssignmentModel.DoesNotExist:
            model = RACIAssignmentMapper.to_model(entity)
        model.save()
        return RACIAssignmentMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[RACIAssignment]:
        try:
            model = RACIAssignmentModel.objects.get(id=id, tenant_id=self._tenant_id)
            return RACIAssignmentMapper.to_entity(model)
        except RACIAssignmentModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[RACIAssignment]:
        qs = RACIAssignmentModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [RACIAssignmentMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = RACIAssignmentModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID, tenant_id: UUID = None) -> List[RACIAssignment]:
        qs = RACIAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, project_id=project_id
        )
        return [RACIAssignmentMapper.to_entity(m) for m in qs]

    def find_by_task(self, task_id: UUID, tenant_id: UUID = None) -> List[RACIAssignment]:
        qs = RACIAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, task_id=task_id
        )
        return [RACIAssignmentMapper.to_entity(m) for m in qs]

    def find_by_user(self, user_id: UUID, project_id: UUID, tenant_id: UUID = None) -> List[RACIAssignment]:
        qs = RACIAssignmentModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            project_id=project_id, user_id=user_id,
        )
        return [RACIAssignmentMapper.to_entity(m) for m in qs]


# ═══════════════════════════════════════════════════
# Activity Code Repositories
# ═══════════════════════════════════════════════════

class DjangoActivityCodeTypeRepository(IActivityCodeTypeRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ActivityCodeType) -> ActivityCodeType:
        try:
            model = ActivityCodeTypeModel.objects.get(id=entity.id)
            model = ActivityCodeTypeMapper.to_model(entity, model)
        except ActivityCodeTypeModel.DoesNotExist:
            model = ActivityCodeTypeMapper.to_model(entity)
        model.save()
        return ActivityCodeTypeMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ActivityCodeType]:
        try:
            model = ActivityCodeTypeModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ActivityCodeTypeMapper.to_entity(model)
        except ActivityCodeTypeModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ActivityCodeType]:
        qs = ActivityCodeTypeModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ActivityCodeTypeMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ActivityCodeTypeModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_scope(self, scope: str, tenant_id: UUID = None) -> List[ActivityCodeType]:
        qs = ActivityCodeTypeModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, scope=scope, is_active=True,
        ).order_by("sort_order")
        return [ActivityCodeTypeMapper.to_entity(m) for m in qs]

    def find_by_project(self, project_id: UUID, tenant_id: UUID = None) -> List[ActivityCodeType]:
        qs = ActivityCodeTypeModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
        ).filter(Q(scope="global") | Q(project_id=project_id)).filter(
            is_active=True
        ).order_by("sort_order")
        return [ActivityCodeTypeMapper.to_entity(m) for m in qs]

    def find_global(self, tenant_id: UUID = None) -> List[ActivityCodeType]:
        qs = ActivityCodeTypeModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            scope="global", is_active=True,
        ).order_by("sort_order")
        return [ActivityCodeTypeMapper.to_entity(m) for m in qs]


class DjangoActivityCodeValueRepository(IActivityCodeValueRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ActivityCodeValue) -> ActivityCodeValue:
        try:
            model = ActivityCodeValueModel.objects.get(id=entity.id)
            model = ActivityCodeValueMapper.to_model(entity, model)
        except ActivityCodeValueModel.DoesNotExist:
            model = ActivityCodeValueMapper.to_model(entity)
        model.save()
        return ActivityCodeValueMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ActivityCodeValue]:
        try:
            model = ActivityCodeValueModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ActivityCodeValueMapper.to_entity(model)
        except ActivityCodeValueModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ActivityCodeValue]:
        qs = ActivityCodeValueModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ActivityCodeValueMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ActivityCodeValueModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_type(self, code_type_id: UUID, tenant_id: UUID = None) -> List[ActivityCodeValue]:
        qs = ActivityCodeValueModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, code_type_id=code_type_id
        ).order_by("sort_order", "code")
        return [ActivityCodeValueMapper.to_entity(m) for m in qs]

    def find_children(self, parent_id: UUID, tenant_id: UUID = None) -> List[ActivityCodeValue]:
        qs = ActivityCodeValueModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, parent_id=parent_id
        ).order_by("sort_order")
        return [ActivityCodeValueMapper.to_entity(m) for m in qs]

    def find_root_values(self, code_type_id: UUID, tenant_id: UUID = None) -> List[ActivityCodeValue]:
        qs = ActivityCodeValueModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            code_type_id=code_type_id, parent__isnull=True,
        ).order_by("sort_order")
        return [ActivityCodeValueMapper.to_entity(m) for m in qs]


class DjangoTaskActivityCodeRepository(ITaskActivityCodeRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: TaskActivityCode) -> TaskActivityCode:
        try:
            model = TaskActivityCodeModel.objects.get(id=entity.id)
            model = TaskActivityCodeMapper.to_model(entity, model)
        except TaskActivityCodeModel.DoesNotExist:
            model = TaskActivityCodeMapper.to_model(entity)
        model.save()
        return TaskActivityCodeMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[TaskActivityCode]:
        try:
            model = TaskActivityCodeModel.objects.get(id=id, tenant_id=self._tenant_id)
            return TaskActivityCodeMapper.to_entity(model)
        except TaskActivityCodeModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[TaskActivityCode]:
        qs = TaskActivityCodeModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [TaskActivityCodeMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = TaskActivityCodeModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_task(self, task_id: UUID, tenant_id: UUID = None) -> List[TaskActivityCode]:
        qs = TaskActivityCodeModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, task_id=task_id
        )
        return [TaskActivityCodeMapper.to_entity(m) for m in qs]

    def find_by_code_value(self, code_value_id: UUID, tenant_id: UUID = None) -> List[TaskActivityCode]:
        qs = TaskActivityCodeModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, code_value_id=code_value_id
        )
        return [TaskActivityCodeMapper.to_entity(m) for m in qs]

    def find_tasks_by_code_type(
        self, code_type_id: UUID, code_value_id: UUID, tenant_id: UUID = None
    ) -> List[TaskActivityCode]:
        qs = TaskActivityCodeModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            code_value_id=code_value_id,
            code_value__code_type_id=code_type_id,
        )
        return [TaskActivityCodeMapper.to_entity(m) for m in qs]


# ═══════════════════════════════════════════════════
# UDF Repositories
# ═══════════════════════════════════════════════════

class DjangoUDFDefinitionRepository(IUDFDefinitionRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: UDFDefinition) -> UDFDefinition:
        try:
            model = UDFDefinitionModel.objects.get(id=entity.id)
            model = UDFDefinitionMapper.to_model(entity, model)
        except UDFDefinitionModel.DoesNotExist:
            model = UDFDefinitionMapper.to_model(entity)
        model.save()
        return UDFDefinitionMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[UDFDefinition]:
        try:
            model = UDFDefinitionModel.objects.get(id=id, tenant_id=self._tenant_id)
            return UDFDefinitionMapper.to_entity(model)
        except UDFDefinitionModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[UDFDefinition]:
        qs = UDFDefinitionModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [UDFDefinitionMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = UDFDefinitionModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_entity_type(self, entity_type: str, tenant_id: UUID = None) -> List[UDFDefinition]:
        qs = UDFDefinitionModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, entity_type=entity_type
        ).order_by("sort_order")
        return [UDFDefinitionMapper.to_entity(m) for m in qs]

    def find_active(self, entity_type: str, tenant_id: UUID = None) -> List[UDFDefinition]:
        qs = UDFDefinitionModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            entity_type=entity_type, is_active=True,
        ).order_by("sort_order")
        return [UDFDefinitionMapper.to_entity(m) for m in qs]

    def find_by_code(self, code: str, tenant_id: UUID = None) -> Optional[UDFDefinition]:
        try:
            model = UDFDefinitionModel.objects.get(
                tenant_id=tenant_id or self._tenant_id, code=code
            )
            return UDFDefinitionMapper.to_entity(model)
        except UDFDefinitionModel.DoesNotExist:
            return None


class DjangoUDFValueRepository(IUDFValueRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: UDFValue) -> UDFValue:
        try:
            model = UDFValueModel.objects.get(id=entity.id)
            model = UDFValueMapper.to_model(entity, model)
        except UDFValueModel.DoesNotExist:
            model = UDFValueMapper.to_model(entity)
        model.save()
        return UDFValueMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[UDFValue]:
        try:
            model = UDFValueModel.objects.get(id=id, tenant_id=self._tenant_id)
            return UDFValueMapper.to_entity(model)
        except UDFValueModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[UDFValue]:
        qs = UDFValueModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [UDFValueMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = UDFValueModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_entity(self, entity_id: UUID, entity_type: str, tenant_id: UUID = None) -> List[UDFValue]:
        qs = UDFValueModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            entity_id=entity_id, entity_type=entity_type,
        )
        return [UDFValueMapper.to_entity(m) for m in qs]

    def find_by_definition(self, definition_id: UUID, tenant_id: UUID = None) -> List[UDFValue]:
        qs = UDFValueModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, definition_id=definition_id
        )
        return [UDFValueMapper.to_entity(m) for m in qs]

    def find_value(
        self, definition_id: UUID, entity_id: UUID, tenant_id: UUID = None
    ) -> Optional[UDFValue]:
        try:
            model = UDFValueModel.objects.get(
                tenant_id=tenant_id or self._tenant_id,
                definition_id=definition_id, entity_id=entity_id,
            )
            return UDFValueMapper.to_entity(model)
        except UDFValueModel.DoesNotExist:
            return None


# ═══════════════════════════════════════════════════
# Advanced Resource Repositories
# ═══════════════════════════════════════════════════

class DjangoResourceSkillRepository(IResourceSkillRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ResourceSkill) -> ResourceSkill:
        try:
            model = ResourceSkillModel.objects.get(id=entity.id)
            model = ResourceSkillMapper.to_model(entity, model)
        except ResourceSkillModel.DoesNotExist:
            model = ResourceSkillMapper.to_model(entity)
        model.save()
        return ResourceSkillMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ResourceSkill]:
        try:
            model = ResourceSkillModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ResourceSkillMapper.to_entity(model)
        except ResourceSkillModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ResourceSkill]:
        qs = ResourceSkillModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ResourceSkillMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ResourceSkillModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_resource(self, resource_id: UUID, tenant_id: UUID = None) -> List[ResourceSkill]:
        qs = ResourceSkillModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, resource_id=resource_id
        )
        return [ResourceSkillMapper.to_entity(m) for m in qs]

    def find_by_skill(self, skill_name: str, tenant_id: UUID = None) -> List[ResourceSkill]:
        qs = ResourceSkillModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            skill_name__icontains=skill_name,
        )
        return [ResourceSkillMapper.to_entity(m) for m in qs]


class DjangoResourceAvailabilityRepository(IResourceAvailabilityRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ResourceAvailability) -> ResourceAvailability:
        try:
            model = ResourceAvailabilityModel.objects.get(id=entity.id)
            model = ResourceAvailabilityMapper.to_model(entity, model)
        except ResourceAvailabilityModel.DoesNotExist:
            model = ResourceAvailabilityMapper.to_model(entity)
        model.save()
        return ResourceAvailabilityMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ResourceAvailability]:
        try:
            model = ResourceAvailabilityModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ResourceAvailabilityMapper.to_entity(model)
        except ResourceAvailabilityModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ResourceAvailability]:
        qs = ResourceAvailabilityModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ResourceAvailabilityMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ResourceAvailabilityModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_resource(self, resource_id: UUID, tenant_id: UUID = None) -> List[ResourceAvailability]:
        qs = ResourceAvailabilityModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, resource_id=resource_id
        ).order_by("start_date")
        return [ResourceAvailabilityMapper.to_entity(m) for m in qs]

    def find_by_date_range(
        self, resource_id: UUID, start_date: date, end_date: date, tenant_id: UUID = None
    ) -> List[ResourceAvailability]:
        qs = ResourceAvailabilityModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            resource_id=resource_id,
            start_date__lte=end_date,
        ).filter(Q(end_date__isnull=True) | Q(end_date__gte=start_date))
        return [ResourceAvailabilityMapper.to_entity(m) for m in qs]


class DjangoResourceCostRateRepository(IResourceCostRateRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ResourceCostRate) -> ResourceCostRate:
        try:
            model = ResourceCostRateModel.objects.get(id=entity.id)
            model = ResourceCostRateMapper.to_model(entity, model)
        except ResourceCostRateModel.DoesNotExist:
            model = ResourceCostRateMapper.to_model(entity)
        model.save()
        return ResourceCostRateMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ResourceCostRate]:
        try:
            model = ResourceCostRateModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ResourceCostRateMapper.to_entity(model)
        except ResourceCostRateModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ResourceCostRate]:
        qs = ResourceCostRateModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ResourceCostRateMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ResourceCostRateModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_resource(self, resource_id: UUID, tenant_id: UUID = None) -> List[ResourceCostRate]:
        qs = ResourceCostRateModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, resource_id=resource_id
        ).order_by("-effective_date")
        return [ResourceCostRateMapper.to_entity(m) for m in qs]

    def find_effective_rate(
        self, resource_id: UUID, on_date: date, tenant_id: UUID = None
    ) -> Optional[ResourceCostRate]:
        try:
            model = ResourceCostRateModel.objects.filter(
                tenant_id=tenant_id or self._tenant_id,
                resource_id=resource_id,
                effective_date__lte=on_date,
            ).order_by("-effective_date").first()
            return ResourceCostRateMapper.to_entity(model) if model else None
        except ResourceCostRateModel.DoesNotExist:
            return None


# ═══════════════════════════════════════════════════
# CBS & Funding Repositories
# ═══════════════════════════════════════════════════

class DjangoCostAccountRepository(ICostAccountRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: CostAccount) -> CostAccount:
        try:
            model = CostAccountModel.objects.get(id=entity.id)
            model = CostAccountMapper.to_model(entity, model)
        except CostAccountModel.DoesNotExist:
            model = CostAccountMapper.to_model(entity)
        model.save()
        return CostAccountMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[CostAccount]:
        try:
            model = CostAccountModel.objects.get(id=id, tenant_id=self._tenant_id)
            return CostAccountMapper.to_entity(model)
        except CostAccountModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[CostAccount]:
        qs = CostAccountModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [CostAccountMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = CostAccountModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID, tenant_id: UUID = None) -> List[CostAccount]:
        qs = CostAccountModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, project_id=project_id
        ).order_by("sort_order", "code")
        return [CostAccountMapper.to_entity(m) for m in qs]

    def find_children(self, parent_id: UUID, tenant_id: UUID = None) -> List[CostAccount]:
        qs = CostAccountModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, parent_id=parent_id
        ).order_by("sort_order")
        return [CostAccountMapper.to_entity(m) for m in qs]

    def find_root_accounts(self, project_id: UUID, tenant_id: UUID = None) -> List[CostAccount]:
        qs = CostAccountModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            project_id=project_id, parent__isnull=True,
        ).order_by("sort_order")
        return [CostAccountMapper.to_entity(m) for m in qs]

    def find_by_code(self, code: str, project_id: UUID, tenant_id: UUID = None) -> Optional[CostAccount]:
        try:
            model = CostAccountModel.objects.get(
                tenant_id=tenant_id or self._tenant_id,
                project_id=project_id, code=code,
            )
            return CostAccountMapper.to_entity(model)
        except CostAccountModel.DoesNotExist:
            return None


class DjangoFundingSourceRepository(IFundingSourceRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: FundingSource) -> FundingSource:
        try:
            model = FundingSourceModel.objects.get(id=entity.id)
            model = FundingSourceMapper.to_model(entity, model)
        except FundingSourceModel.DoesNotExist:
            model = FundingSourceMapper.to_model(entity)
        model.save()
        return FundingSourceMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[FundingSource]:
        try:
            model = FundingSourceModel.objects.get(id=id, tenant_id=self._tenant_id)
            return FundingSourceMapper.to_entity(model)
        except FundingSourceModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[FundingSource]:
        qs = FundingSourceModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [FundingSourceMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = FundingSourceModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID, tenant_id: UUID = None) -> List[FundingSource]:
        qs = FundingSourceModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, project_id=project_id
        )
        return [FundingSourceMapper.to_entity(m) for m in qs]

    def find_active(self, project_id: UUID, tenant_id: UUID = None) -> List[FundingSource]:
        qs = FundingSourceModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            project_id=project_id, is_active=True,
        )
        return [FundingSourceMapper.to_entity(m) for m in qs]


class DjangoCashFlowEntryRepository(ICashFlowEntryRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: CashFlowEntry) -> CashFlowEntry:
        try:
            model = CashFlowEntryModel.objects.get(id=entity.id)
            model = CashFlowEntryMapper.to_model(entity, model)
        except CashFlowEntryModel.DoesNotExist:
            model = CashFlowEntryMapper.to_model(entity)
        model.save()
        return CashFlowEntryMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[CashFlowEntry]:
        try:
            model = CashFlowEntryModel.objects.get(id=id, tenant_id=self._tenant_id)
            return CashFlowEntryMapper.to_entity(model)
        except CashFlowEntryModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[CashFlowEntry]:
        qs = CashFlowEntryModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [CashFlowEntryMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = CashFlowEntryModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID, tenant_id: UUID = None) -> List[CashFlowEntry]:
        qs = CashFlowEntryModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, project_id=project_id
        ).order_by("period_start")
        return [CashFlowEntryMapper.to_entity(m) for m in qs]

    def find_by_period(
        self, project_id: UUID, start_date: date, end_date: date, tenant_id: UUID = None
    ) -> List[CashFlowEntry]:
        qs = CashFlowEntryModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            project_id=project_id,
            period_start__gte=start_date,
            period_start__lte=end_date,
        ).order_by("period_start")
        return [CashFlowEntryMapper.to_entity(m) for m in qs]


# ═══════════════════════════════════════════════════
# Schedule Health & Template Repositories
# ═══════════════════════════════════════════════════

class DjangoScheduleHealthSnapshotRepository(IScheduleHealthSnapshotRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ScheduleHealthSnapshot) -> ScheduleHealthSnapshot:
        try:
            model = ScheduleHealthSnapshotModel.objects.get(id=entity.id)
            model = ScheduleHealthSnapshotMapper.to_model(entity, model)
        except ScheduleHealthSnapshotModel.DoesNotExist:
            model = ScheduleHealthSnapshotMapper.to_model(entity)
        model.save()
        return ScheduleHealthSnapshotMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ScheduleHealthSnapshot]:
        try:
            model = ScheduleHealthSnapshotModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ScheduleHealthSnapshotMapper.to_entity(model)
        except ScheduleHealthSnapshotModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ScheduleHealthSnapshot]:
        qs = ScheduleHealthSnapshotModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ScheduleHealthSnapshotMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ScheduleHealthSnapshotModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_by_project(self, project_id: UUID, tenant_id: UUID = None) -> List[ScheduleHealthSnapshot]:
        qs = ScheduleHealthSnapshotModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, project_id=project_id
        ).order_by("-snapshot_date")
        return [ScheduleHealthSnapshotMapper.to_entity(m) for m in qs]

    def find_latest(self, project_id: UUID, tenant_id: UUID = None) -> Optional[ScheduleHealthSnapshot]:
        model = ScheduleHealthSnapshotModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, project_id=project_id
        ).order_by("-snapshot_date").first()
        return ScheduleHealthSnapshotMapper.to_entity(model) if model else None

    def find_by_date_range(
        self, project_id: UUID, start_date: date, end_date: date, tenant_id: UUID = None
    ) -> List[ScheduleHealthSnapshot]:
        qs = ScheduleHealthSnapshotModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            project_id=project_id,
            snapshot_date__gte=start_date,
            snapshot_date__lte=end_date,
        ).order_by("snapshot_date")
        return [ScheduleHealthSnapshotMapper.to_entity(m) for m in qs]


class DjangoProjectTemplateRepository(IProjectTemplateRepository):

    def __init__(self, tenant_id: UUID):
        self._tenant_id = tenant_id

    def save(self, entity: ProjectTemplate) -> ProjectTemplate:
        try:
            model = ProjectTemplateModel.objects.get(id=entity.id)
            model = ProjectTemplateMapper.to_model(entity, model)
        except ProjectTemplateModel.DoesNotExist:
            model = ProjectTemplateMapper.to_model(entity)
        model.save()
        return ProjectTemplateMapper.to_entity(model)

    def get_by_id(self, id: UUID) -> Optional[ProjectTemplate]:
        try:
            model = ProjectTemplateModel.objects.get(id=id, tenant_id=self._tenant_id)
            return ProjectTemplateMapper.to_entity(model)
        except ProjectTemplateModel.DoesNotExist:
            return None

    def find(self, **criteria) -> List[ProjectTemplate]:
        qs = ProjectTemplateModel.objects.filter(tenant_id=self._tenant_id, **criteria)
        return [ProjectTemplateMapper.to_entity(m) for m in qs]

    def delete(self, id: UUID) -> bool:
        deleted, _ = ProjectTemplateModel.objects.filter(
            id=id, tenant_id=self._tenant_id
        ).delete()
        return deleted > 0

    def find_active(self, tenant_id: UUID = None) -> List[ProjectTemplate]:
        qs = ProjectTemplateModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id, is_active=True
        ).order_by("-usage_count", "name")
        return [ProjectTemplateMapper.to_entity(m) for m in qs]

    def find_by_category(self, category: str, tenant_id: UUID = None) -> List[ProjectTemplate]:
        qs = ProjectTemplateModel.objects.filter(
            tenant_id=tenant_id or self._tenant_id,
            category=category, is_active=True,
        ).order_by("-usage_count")
        return [ProjectTemplateMapper.to_entity(m) for m in qs]
