"""
Workflow Managers.

Custom QuerySets and Managers for workflow models.
"""
from django.db import models
from django.db.models import Q, Count, Prefetch
from django.utils import timezone

from apps.core.tenant.models import TenantAwareManager


class ProcessDefinitionQuerySet(models.QuerySet):
    """Custom QuerySet for ProcessDefinition model."""

    def deployed(self):
        """Filter deployed process definitions."""
        return self.filter(is_deployed=True)

    def draft(self):
        """Filter draft (not deployed) process definitions."""
        return self.filter(is_deployed=False)

    def active(self):
        """Filter active process definitions."""
        return self.filter(is_active=True)

    def inactive(self):
        """Filter inactive process definitions."""
        return self.filter(is_active=False)

    def startable(self):
        """Get process definitions that can be started (deployed and active)."""
        return self.filter(is_deployed=True, is_active=True)

    def by_slug(self, slug: str):
        """Filter by process slug."""
        return self.filter(slug=slug)

    def latest_version(self, slug: str):
        """Get the latest version of a process by slug."""
        return self.filter(slug=slug).order_by('-version').first()

    def with_instances_count(self):
        """Annotate with count of instances."""
        return self.annotate(instances_count=Count('instances'))

    def with_sla_definitions(self):
        """Prefetch related SLA definitions."""
        return self.prefetch_related('sla_definitions')


class ProcessDefinitionManager(TenantAwareManager):
    """Custom Manager for ProcessDefinition model."""

    def get_queryset(self):
        return ProcessDefinitionQuerySet(self.model, using=self._db)

    def deployed(self):
        return self.get_queryset().deployed()

    def draft(self):
        return self.get_queryset().draft()

    def active(self):
        return self.get_queryset().active()

    def inactive(self):
        return self.get_queryset().inactive()

    def startable(self):
        return self.get_queryset().startable()

    def by_slug(self, slug: str):
        return self.get_queryset().by_slug(slug)

    def get_latest_version(self, slug: str):
        """Get the latest version of a process definition by slug."""
        return self.get_queryset().by_slug(slug).order_by('-version').first()

    def get_startable_by_slug(self, slug: str):
        """Get the startable process definition by slug (latest deployed + active)."""
        return self.get_queryset().startable().by_slug(slug).order_by('-version').first()


class ProcessInstanceQuerySet(models.QuerySet):
    """Custom QuerySet for ProcessInstance model."""

    def running(self):
        """Filter running instances."""
        from .models import InstanceStatus
        return self.filter(status=InstanceStatus.RUNNING)

    def completed(self):
        """Filter completed instances."""
        from .models import InstanceStatus
        return self.filter(status=InstanceStatus.COMPLETED)

    def failed(self):
        """Filter failed instances."""
        from .models import InstanceStatus
        return self.filter(status=InstanceStatus.FAILED)

    def cancelled(self):
        """Filter cancelled instances."""
        from .models import InstanceStatus
        return self.filter(status=InstanceStatus.CANCELLED)

    def suspended(self):
        """Filter suspended instances."""
        from .models import InstanceStatus
        return self.filter(status=InstanceStatus.SUSPENDED)

    def active(self):
        """Filter active instances (running or suspended)."""
        from .models import InstanceStatus
        return self.filter(
            status__in=[InstanceStatus.RUNNING, InstanceStatus.SUSPENDED]
        )

    def finished(self):
        """Filter finished instances (completed, failed, or cancelled)."""
        from .models import InstanceStatus
        return self.filter(
            status__in=[
                InstanceStatus.COMPLETED,
                InstanceStatus.FAILED,
                InstanceStatus.CANCELLED
            ]
        )

    def by_definition(self, definition):
        """Filter by process definition."""
        return self.filter(definition=definition)

    def by_definition_slug(self, slug: str):
        """Filter by process definition slug."""
        return self.filter(definition__slug=slug)

    def by_business_key(self, business_key: str):
        """Filter by business key."""
        return self.filter(business_key=business_key)

    def started_by(self, user):
        """Filter by started_by user."""
        return self.filter(started_by=user)

    def with_tasks(self):
        """Prefetch related tasks."""
        return self.prefetch_related('tasks')

    def with_active_tasks(self):
        """Prefetch only active tasks."""
        from .models import Task, TaskStatus
        return self.prefetch_related(
            Prefetch(
                'tasks',
                queryset=Task.objects.filter(
                    status__in=[TaskStatus.CREATED, TaskStatus.ASSIGNED, TaskStatus.CLAIMED]
                ),
                to_attr='active_tasks'
            )
        )

    def with_variables(self):
        """Prefetch related variables."""
        return self.prefetch_related('variables')

    def with_history(self):
        """Prefetch related history."""
        return self.prefetch_related('history')

    def with_definition(self):
        """Select related definition."""
        return self.select_related('definition')


class ProcessInstanceManager(TenantAwareManager):
    """Custom Manager for ProcessInstance model."""

    def get_queryset(self):
        return ProcessInstanceQuerySet(self.model, using=self._db)

    def running(self):
        return self.get_queryset().running()

    def completed(self):
        return self.get_queryset().completed()

    def failed(self):
        return self.get_queryset().failed()

    def cancelled(self):
        return self.get_queryset().cancelled()

    def suspended(self):
        return self.get_queryset().suspended()

    def active(self):
        return self.get_queryset().active()

    def finished(self):
        return self.get_queryset().finished()

    def by_definition(self, definition):
        return self.get_queryset().by_definition(definition)

    def by_definition_slug(self, slug: str):
        return self.get_queryset().by_definition_slug(slug)

    def by_business_key(self, business_key: str):
        return self.get_queryset().by_business_key(business_key)

    def started_by(self, user):
        return self.get_queryset().started_by(user)


class TaskQuerySet(models.QuerySet):
    """Custom QuerySet for Task model."""

    def created(self):
        """Filter created tasks."""
        from .models import TaskStatus
        return self.filter(status=TaskStatus.CREATED)

    def assigned(self):
        """Filter assigned tasks."""
        from .models import TaskStatus
        return self.filter(status=TaskStatus.ASSIGNED)

    def claimed(self):
        """Filter claimed tasks."""
        from .models import TaskStatus
        return self.filter(status=TaskStatus.CLAIMED)

    def completed(self):
        """Filter completed tasks."""
        from .models import TaskStatus
        return self.filter(status=TaskStatus.COMPLETED)

    def failed(self):
        """Filter failed tasks."""
        from .models import TaskStatus
        return self.filter(status=TaskStatus.FAILED)

    def cancelled(self):
        """Filter cancelled tasks."""
        from .models import TaskStatus
        return self.filter(status=TaskStatus.CANCELLED)

    def active(self):
        """Filter active tasks (created, assigned, or claimed)."""
        from .models import TaskStatus
        return self.filter(
            status__in=[TaskStatus.CREATED, TaskStatus.ASSIGNED, TaskStatus.CLAIMED]
        )

    def claimable(self):
        """Filter tasks that can be claimed (created or assigned without assignee)."""
        from .models import TaskStatus
        return self.filter(
            status__in=[TaskStatus.CREATED, TaskStatus.ASSIGNED],
            assignee__isnull=True
        )

    def assigned_to(self, user):
        """Filter tasks assigned to a specific user."""
        return self.filter(assignee=user)

    def candidate_for(self, user):
        """Filter tasks where user is a candidate (through assignments)."""
        from .models import TaskAssignment
        return self.filter(
            Q(assignments__user=user, assignments__is_active=True) |
            Q(assignments__group__in=user.groups.all(), assignments__is_active=True)
        ).distinct()

    def available_for(self, user):
        """Filter tasks available for a user (assigned to or candidate for)."""
        from .models import TaskAssignment
        return self.filter(
            Q(assignee=user) |
            Q(assignments__user=user, assignments__is_active=True) |
            Q(assignments__group__in=user.groups.all(), assignments__is_active=True)
        ).distinct()

    def overdue(self):
        """Filter overdue tasks."""
        return self.filter(
            due_date__isnull=False,
            due_date__lt=timezone.now()
        ).active()

    def due_soon(self, hours: int = 24):
        """Filter tasks due within specified hours."""
        threshold = timezone.now() + timezone.timedelta(hours=hours)
        return self.filter(
            due_date__isnull=False,
            due_date__lte=threshold,
            due_date__gt=timezone.now()
        ).active()

    def by_instance(self, instance):
        """Filter by process instance."""
        return self.filter(instance=instance)

    def by_task_type(self, task_type: str):
        """Filter by task type."""
        return self.filter(task_type=task_type)

    def user_tasks(self):
        """Filter user tasks only."""
        from .models import TaskType
        return self.filter(task_type=TaskType.USER_TASK)

    def service_tasks(self):
        """Filter service tasks only."""
        from .models import TaskType
        return self.filter(task_type=TaskType.SERVICE_TASK)

    def script_tasks(self):
        """Filter script tasks only."""
        from .models import TaskType
        return self.filter(task_type=TaskType.SCRIPT_TASK)

    def with_instance(self):
        """Select related process instance."""
        return self.select_related('instance', 'instance__definition')

    def with_assignee(self):
        """Select related assignee."""
        return self.select_related('assignee')

    def with_assignments(self):
        """Prefetch related assignments."""
        return self.prefetch_related('assignments', 'assignments__user', 'assignments__group')

    def by_priority(self):
        """Order by priority (highest first)."""
        return self.order_by('-priority', '-created_at')


class TaskManager(TenantAwareManager):
    """Custom Manager for Task model."""

    def get_queryset(self):
        return TaskQuerySet(self.model, using=self._db)

    def created(self):
        return self.get_queryset().created()

    def assigned(self):
        return self.get_queryset().assigned()

    def claimed(self):
        return self.get_queryset().claimed()

    def completed(self):
        return self.get_queryset().completed()

    def failed(self):
        return self.get_queryset().failed()

    def cancelled(self):
        return self.get_queryset().cancelled()

    def active(self):
        return self.get_queryset().active()

    def claimable(self):
        return self.get_queryset().claimable()

    def assigned_to(self, user):
        return self.get_queryset().assigned_to(user)

    def candidate_for(self, user):
        return self.get_queryset().candidate_for(user)

    def available_for(self, user):
        return self.get_queryset().available_for(user)

    def overdue(self):
        return self.get_queryset().overdue()

    def due_soon(self, hours: int = 24):
        return self.get_queryset().due_soon(hours)

    def by_instance(self, instance):
        return self.get_queryset().by_instance(instance)

    def user_tasks(self):
        return self.get_queryset().user_tasks()

    def get_inbox(self, user):
        """Get user's task inbox (available tasks)."""
        return self.get_queryset().available_for(user).active().select_related(
            'instance',
            'instance__definition'
        ).by_priority()

    def get_my_tasks(self, user):
        """Get tasks assigned to/claimed by user."""
        return self.get_queryset().assigned_to(user).active().select_related(
            'instance',
            'instance__definition'
        ).by_priority()


class ProcessHistoryQuerySet(models.QuerySet):
    """Custom QuerySet for ProcessHistory model."""

    def by_event_type(self, event_type: str):
        """Filter by event type."""
        return self.filter(event_type=event_type)

    def by_user(self, user):
        """Filter by user."""
        return self.filter(user=user)

    def by_instance(self, instance):
        """Filter by process instance."""
        return self.filter(instance=instance)

    def by_task(self, task):
        """Filter by task."""
        return self.filter(task=task)

    def recent(self, limit: int = 50):
        """Get recent history entries."""
        return self.order_by('-created_at')[:limit]

    def in_date_range(self, start_date, end_date):
        """Filter by date range."""
        return self.filter(created_at__gte=start_date, created_at__lte=end_date)

    def with_user(self):
        """Select related user."""
        return self.select_related('user')


class ProcessHistoryManager(models.Manager):
    """Custom Manager for ProcessHistory model."""

    def get_queryset(self):
        return ProcessHistoryQuerySet(self.model, using=self._db)

    def by_event_type(self, event_type: str):
        return self.get_queryset().by_event_type(event_type)

    def by_user(self, user):
        return self.get_queryset().by_user(user)

    def by_instance(self, instance):
        return self.get_queryset().by_instance(instance)

    def by_task(self, task):
        return self.get_queryset().by_task(task)

    def recent(self, limit: int = 50):
        return self.get_queryset().recent(limit)

    def log_event(
        self,
        instance,
        event_type: str,
        user=None,
        task=None,
        element_id: str = None,
        old_value: dict = None,
        new_value: dict = None,
        comment: str = ''
    ):
        """Create a history log entry."""
        return self.create(
            instance=instance,
            event_type=event_type,
            user=user,
            task=task,
            element_id=element_id,
            old_value=old_value,
            new_value=new_value,
            comment=comment
        )


class ProcessVariableQuerySet(models.QuerySet):
    """Custom QuerySet for ProcessVariable model."""

    def by_name(self, name: str):
        """Filter by variable name."""
        return self.filter(name=name)

    def by_type(self, var_type: str):
        """Filter by variable type."""
        return self.filter(type=var_type)

    def by_instance(self, instance):
        """Filter by process instance."""
        return self.filter(instance=instance)

    def global_scope(self):
        """Filter global scope variables."""
        return self.filter(scope__isnull=True)

    def local_scope(self, element_id: str):
        """Filter variables scoped to a specific element."""
        return self.filter(scope=element_id)

    def as_dict(self):
        """Convert queryset to dictionary of name: value pairs."""
        return {var.name: var.value for var in self}


class ProcessVariableManager(models.Manager):
    """Custom Manager for ProcessVariable model."""

    def get_queryset(self):
        return ProcessVariableQuerySet(self.model, using=self._db)

    def by_name(self, name: str):
        return self.get_queryset().by_name(name)

    def by_type(self, var_type: str):
        return self.get_queryset().by_type(var_type)

    def by_instance(self, instance):
        return self.get_queryset().by_instance(instance)

    def global_scope(self):
        return self.get_queryset().global_scope()

    def get_value(self, instance, name: str, scope: str = None, default=None):
        """Get a variable value by name."""
        try:
            variable = self.get(instance=instance, name=name, scope=scope)
            return variable.value
        except self.model.DoesNotExist:
            return default

    def set_value(self, instance, name: str, value, var_type: str = 'string', scope: str = None):
        """Set a variable value, creating if necessary."""
        variable, created = self.update_or_create(
            instance=instance,
            name=name,
            scope=scope,
            defaults={
                'value': value,
                'type': var_type
            }
        )
        return variable

    def get_all_as_dict(self, instance, scope: str = None):
        """Get all variables for a process instance as a dictionary."""
        qs = self.by_instance(instance)
        if scope:
            qs = qs.filter(Q(scope__isnull=True) | Q(scope=scope))
        else:
            qs = qs.global_scope()
        return qs.as_dict()


class SLADefinitionQuerySet(models.QuerySet):
    """Custom QuerySet for SLADefinition model."""

    def active(self):
        """Filter active SLA definitions."""
        return self.filter(is_active=True)

    def by_definition(self, definition):
        """Filter by process definition."""
        return self.filter(definition=definition)

    def by_task_key(self, task_definition_key: str):
        """Filter by task definition key."""
        return self.filter(task_definition_key=task_definition_key)


class SLADefinitionManager(models.Manager):
    """Custom Manager for SLADefinition model."""

    def get_queryset(self):
        return SLADefinitionQuerySet(self.model, using=self._db)

    def active(self):
        return self.get_queryset().active()

    def by_definition(self, definition):
        return self.get_queryset().by_definition(definition)

    def by_task_key(self, task_definition_key: str):
        return self.get_queryset().by_task_key(task_definition_key)

    def get_for_task(self, definition, task_definition_key: str):
        """Get the SLA definition for a specific task."""
        return self.active().filter(
            definition=definition,
            task_definition_key=task_definition_key
        ).first()


class SLAViolationQuerySet(models.QuerySet):
    """Custom QuerySet for SLAViolation model."""

    def resolved(self):
        """Filter resolved violations."""
        return self.filter(is_resolved=True)

    def unresolved(self):
        """Filter unresolved violations."""
        return self.filter(is_resolved=False)

    def by_task(self, task):
        """Filter by task."""
        return self.filter(task=task)

    def by_sla_definition(self, sla_definition):
        """Filter by SLA definition."""
        return self.filter(sla_definition=sla_definition)

    def recent(self, limit: int = 50):
        """Get recent violations."""
        return self.order_by('-violation_time')[:limit]


class SLAViolationManager(models.Manager):
    """Custom Manager for SLAViolation model."""

    def get_queryset(self):
        return SLAViolationQuerySet(self.model, using=self._db)

    def resolved(self):
        return self.get_queryset().resolved()

    def unresolved(self):
        return self.get_queryset().unresolved()

    def by_task(self, task):
        return self.get_queryset().by_task(task)

    def by_sla_definition(self, sla_definition):
        return self.get_queryset().by_sla_definition(sla_definition)

    def recent(self, limit: int = 50):
        return self.get_queryset().recent(limit)

    def record_violation(self, task, sla_definition, expected_completion):
        """Record a new SLA violation."""
        return self.create(
            task=task,
            sla_definition=sla_definition,
            expected_completion=expected_completion
        )


class TaskAssignmentQuerySet(models.QuerySet):
    """Custom QuerySet for TaskAssignment model."""

    def active(self):
        """Filter active assignments."""
        return self.filter(is_active=True)

    def inactive(self):
        """Filter inactive assignments."""
        return self.filter(is_active=False)

    def by_user(self, user):
        """Filter by user."""
        return self.filter(user=user)

    def by_group(self, group):
        """Filter by group."""
        return self.filter(group=group)

    def by_task(self, task):
        """Filter by task."""
        return self.filter(task=task)

    def by_type(self, assignment_type: str):
        """Filter by assignment type."""
        return self.filter(assignment_type=assignment_type)

    def candidates(self):
        """Filter candidate assignments."""
        from .models import AssignmentType
        return self.filter(assignment_type=AssignmentType.CANDIDATE)

    def with_user(self):
        """Select related user."""
        return self.select_related('user')

    def with_group(self):
        """Select related group."""
        return self.select_related('group')

    def with_task(self):
        """Select related task."""
        return self.select_related('task')


class TaskAssignmentManager(models.Manager):
    """Custom Manager for TaskAssignment model."""

    def get_queryset(self):
        return TaskAssignmentQuerySet(self.model, using=self._db)

    def active(self):
        return self.get_queryset().active()

    def inactive(self):
        return self.get_queryset().inactive()

    def by_user(self, user):
        return self.get_queryset().by_user(user)

    def by_group(self, group):
        return self.get_queryset().by_group(group)

    def by_task(self, task):
        return self.get_queryset().by_task(task)

    def by_type(self, assignment_type: str):
        return self.get_queryset().by_type(assignment_type)

    def candidates(self):
        return self.get_queryset().candidates()

    def assign_user(self, task, user, assignment_type: str = None):
        """Assign a user to a task."""
        from .models import AssignmentType
        assignment_type = assignment_type or AssignmentType.CANDIDATE
        assignment, created = self.get_or_create(
            task=task,
            user=user,
            defaults={
                'assignment_type': assignment_type,
                'is_active': True
            }
        )
        return assignment, created

    def assign_group(self, task, group, assignment_type: str = None):
        """Assign a group to a task."""
        from .models import AssignmentType
        assignment_type = assignment_type or AssignmentType.CANDIDATE
        assignment, created = self.get_or_create(
            task=task,
            group=group,
            defaults={
                'assignment_type': assignment_type,
                'is_active': True
            }
        )
        return assignment, created

    def remove_user(self, task, user):
        """Remove a user from a task (deactivate)."""
        return self.filter(task=task, user=user).update(
            is_active=False,
            unassigned_at=timezone.now()
        )

    def remove_group(self, task, group):
        """Remove a group from a task (deactivate)."""
        return self.filter(task=task, group=group).update(
            is_active=False,
            unassigned_at=timezone.now()
        )
