"""WorkItem platform services — canonical WorkItem management.

The WorkItem is the unified actionable object used by all platform engines:

* Assignment Engine  — creates WorkItem(item_type="assignment")
* Approval Engine    — creates WorkItem(item_type="approval")
* Task Module        — creates WorkItem(item_type="task")
* Workflow Engine    — creates WorkItem(item_type="workflow_step")
* Helpdesk           — creates WorkItem(item_type="ticket")
* CRM / HR / DMS     — creates WorkItem(item_type="review" / "custom")

All write operations go through these functions. Views must NOT call ORM
save/delete directly on WorkItem models.
"""

from __future__ import annotations

from datetime import datetime
from typing import Any

from django.db import transaction
from django.utils import timezone

from simorgh.apps.workbox.models import (
    WorkboxItemStatus,
    WorkboxItemType,
    WorkItem,
    WorkItemTimeline,
)

# ──────────────────────────────────────────────────────────────────────────
# WorkItemService — canonical CRUD
# ──────────────────────────────────────────────────────────────────────────

class WorkItemService:
    """Stateless service for canonical WorkItem operations.

    Engines call these methods to create and manage WorkItems as the
    platform-level source of truth.
    """

    @staticmethod
    def create_work_item(
        *,
        tenant_id: int,
        organization_node_id: int,
        item_type: str,
        title: str,
        assigned_to_user_id: int | None = None,
        assigned_to_role_id: int | None = None,
        assigned_to_unit_id: int | None = None,
        summary: str = "",
        description: str = "",
        priority: str = "medium",
        source_type: str = "",
        source_entity: str = "",
        source_id: str = "",
        source_url: str = "",
        due_date: datetime | None = None,
        is_delegated: bool = False,
        metadata: dict | None = None,
    ) -> WorkItem:
        """Create a canonical WorkItem and dispatch the ``workitem_created`` event."""
        from simorgh.apps.events.bus import dispatch
        from simorgh.core.audit import record_service_event

        with transaction.atomic():
            work_item = WorkItem.objects.create(
                tenant_id=tenant_id,
                organization_node_id=organization_node_id,
                item_type=item_type,
                title=title.strip(),
                summary=summary,
                description=description,
                status=WorkboxItemStatus.PENDING,
                priority=priority,
                assigned_to_user_id=assigned_to_user_id,
                assigned_to_role_id=assigned_to_role_id,
                assigned_to_unit_id=assigned_to_unit_id,
                source_type=source_type,
                source_entity=source_entity,
                source_id=source_id,
                source_url=source_url,
                due_date=due_date,
                is_delegated=is_delegated,
                metadata=metadata or {},
            )
            WorkItemTimeline.objects.create(
                work_item=work_item,
                event="created",
                to_status=work_item.status,
                actor_id=assigned_to_user_id,
            )

        record_service_event("workitem.created", resource=work_item, after={
            "item_type": item_type,
            "assigned_to_user_id": assigned_to_user_id,
        })
        dispatch("workbox.workitem_created", {
            "item_id": str(work_item.public_id),
            "assigned_to_user_id": assigned_to_user_id,
            "item_type": item_type,
            "tenant_id": tenant_id,
        })
        return work_item

    @staticmethod
    def complete_work_item(
        work_item: WorkItem,
        *,
        actor_id: int,
        note: str = "",
    ) -> WorkItem:
        """Mark a WorkItem as completed and dispatch ``workitem_completed``."""
        from simorgh.apps.events.bus import dispatch
        from simorgh.core.audit import record_service_event

        if work_item.status in (WorkboxItemStatus.COMPLETED, WorkboxItemStatus.CANCELLED, WorkboxItemStatus.EXPIRED):
            raise ValueError(f"Cannot complete a WorkItem with status '{work_item.status}'.")

        previous_status = work_item.status
        with transaction.atomic():
            work_item.status = WorkboxItemStatus.COMPLETED
            work_item.completed_at = timezone.now()
            work_item.save(update_fields=["status", "completed_at", "updated_at"])
            WorkItemTimeline.objects.create(
                work_item=work_item,
                event="completed",
                from_status=previous_status,
                to_status=work_item.status,
                actor_id=actor_id,
                note=note,
            )

        record_service_event("workitem.completed", resource=work_item, after={
            "from_status": previous_status,
            "actor_id": actor_id,
        })
        dispatch("workbox.workitem_completed", {
            "item_id": str(work_item.public_id),
            "assigned_to_user_id": work_item.assigned_to_user_id,
            "tenant_id": work_item.tenant_id,
        })
        return work_item

    @staticmethod
    def cancel_work_item(
        work_item: WorkItem,
        *,
        actor_id: int,
        note: str = "",
    ) -> WorkItem:
        """Cancel a WorkItem and dispatch ``workitem_cancelled``."""
        from simorgh.apps.events.bus import dispatch
        from simorgh.core.audit import record_service_event

        if work_item.status in (WorkboxItemStatus.COMPLETED, WorkboxItemStatus.CANCELLED, WorkboxItemStatus.EXPIRED):
            raise ValueError(f"Cannot cancel a WorkItem with status '{work_item.status}'.")

        previous_status = work_item.status
        with transaction.atomic():
            work_item.status = WorkboxItemStatus.CANCELLED
            work_item.save(update_fields=["status", "updated_at"])
            WorkItemTimeline.objects.create(
                work_item=work_item,
                event="cancelled",
                from_status=previous_status,
                to_status=work_item.status,
                actor_id=actor_id,
                note=note,
            )

        record_service_event("workitem.cancelled", resource=work_item)
        dispatch("workbox.workitem_cancelled", {
            "item_id": str(work_item.public_id),
            "cancelled_by_id": actor_id,
            "tenant_id": work_item.tenant_id,
        })
        return work_item

    @staticmethod
    def reassign_work_item(
        work_item: WorkItem,
        *,
        to_user_id: int | None = None,
        to_role_id: int | None = None,
        to_unit_id: int | None = None,
        actor_id: int,
        note: str = "",
    ) -> WorkItem:
        """Reassign a WorkItem to a different user/role/unit and dispatch ``workitem_reassigned``."""
        from simorgh.apps.events.bus import dispatch
        from simorgh.core.audit import record_service_event

        if work_item.status in (WorkboxItemStatus.COMPLETED, WorkboxItemStatus.CANCELLED, WorkboxItemStatus.EXPIRED):
            raise ValueError(f"Cannot reassign a WorkItem with status '{work_item.status}'.")

        from_user_id = work_item.assigned_to_user_id
        from_role_id = work_item.assigned_to_role_id
        from_unit_id = work_item.assigned_to_unit_id

        work_item.assigned_to_user_id = to_user_id
        work_item.assigned_to_role_id = to_role_id
        work_item.assigned_to_unit_id = to_unit_id
        work_item.save(update_fields=["assigned_to_user_id", "assigned_to_role_id", "assigned_to_unit_id", "updated_at"])

        WorkItemTimeline.objects.create(
            work_item=work_item,
            event="reassigned",
            from_status=work_item.status,
            to_status=work_item.status,
            actor_id=actor_id,
            note=note,
            metadata={
                "from_user_id": from_user_id,
                "to_user_id": to_user_id,
                "from_role_id": from_role_id,
                "to_role_id": to_role_id,
                "from_unit_id": from_unit_id,
                "to_unit_id": to_unit_id,
            },
        )

        record_service_event("workitem.reassigned", resource=work_item, after={
            "from_user_id": from_user_id,
            "to_user_id": to_user_id,
        })
        dispatch("workbox.workitem_reassigned", {
            "item_id": str(work_item.public_id),
            "from_user_id": from_user_id,
            "to_user_id": to_user_id,
            "tenant_id": work_item.tenant_id,
        })
        return work_item

    @staticmethod
    def expire_work_items() -> int:
        """Auto-expire WorkItems past their due_date. Returns count of expired items."""
        from simorgh.apps.events.bus import dispatch

        expired = WorkItem.objects.filter(
            status__in=(WorkboxItemStatus.PENDING, WorkboxItemStatus.IN_PROGRESS, WorkboxItemStatus.OVERDUE),
            due_date__lt=timezone.now(),
        )
        count = 0
        for item in expired:
            previous_status = item.status
            item.status = WorkboxItemStatus.EXPIRED
            item.save(update_fields=["status", "updated_at"])
            WorkItemTimeline.objects.create(
                work_item=item,
                event="expired",
                from_status=previous_status,
                to_status=item.status,
            )
            dispatch("workbox.workitem_expired", {
                "item_id": str(item.public_id),
                "assigned_to_user_id": item.assigned_to_user_id,
                "tenant_id": item.tenant_id,
            })
            count += 1
        return count

    @staticmethod
    def get_timeline(work_item: WorkItem) -> list[dict]:
        """Return timeline entries for a WorkItem as a list of dicts."""
        entries = work_item.timeline.select_related("actor").order_by("-created_at")
        return [
            {
                "event": e.event,
                "from_status": e.from_status,
                "to_status": e.to_status,
                "actor": getattr(e.actor, "get_full_name", lambda: "")() or getattr(e.actor, "email", "") if e.actor else "",
                "note": e.note,
                "metadata": e.metadata,
                "created_at": e.created_at.isoformat(),
            }
            for e in entries
        ]


# ──────────────────────────────────────────────────────────────────────────
# Backward-compatible workbox sync functions
# ──────────────────────────────────────────────────────────────────────────

def _make_item(**kwargs: Any) -> WorkItem:
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    with transaction.atomic():
        item = WorkItem.objects.create(**kwargs)

    record_service_event("workbox.item.created", resource=item, after={
        "item_type": item.item_type,
        "assigned_to_user_id": item.assigned_to_user_id,
    })
    dispatch("workbox.item_created", {
        "item_id": str(item.public_id),
        "user_id": item.assigned_to_user_id,
        "item_type": item.item_type,
        "tenant_id": item.tenant_id,
    })
    return item


def upsert_workbox_item(
    *,
    tenant_id: int,
    organization_node_id: int,
    user_id: int,
    item_type: str,
    source_entity: str,
    source_id: str,
    title: str,
    description: str = "",
    status: str = WorkboxItemStatus.PENDING,
    priority: str = "medium",
    source_url: str = "",
    due_date: datetime | None = None,
    completed_at: datetime | None = None,
    is_delegated: bool = False,
    metadata: dict | None = None,
) -> WorkItem:
    """Create or update a WorkItem from source engines (backward compatible).

    Uses ``source_entity`` + ``source_id`` as the unique key within a tenant
    so refreshes from source services are idempotent.
    """
    from simorgh.apps.events.bus import dispatch
    from simorgh.core.audit import record_service_event

    with transaction.atomic():
        item, created = WorkItem.objects.update_or_create(
            tenant_id=tenant_id,
            source_entity=source_entity,
            source_id=source_id,
            defaults={
                "organization_node_id": organization_node_id,
                "assigned_to_user_id": user_id,
                "item_type": item_type,
                "title": title,
                "description": description,
                "status": status,
                "priority": priority,
                "source_url": source_url,
                "due_date": due_date,
                "completed_at": completed_at,
                "is_delegated": is_delegated,
                "metadata": metadata or {},
            },
        )

    if created:
        record_service_event("workbox.item.created", resource=item, after={
            "item_type": item_type,
            "user_id": user_id,
        })
        dispatch("workbox.item_created", {
            "item_id": str(item.public_id),
            "user_id": user_id,
            "item_type": item_type,
            "tenant_id": tenant_id,
        })
    else:
        dispatch("workbox.item_updated", {
            "item_id": str(item.public_id),
            "user_id": user_id,
            "tenant_id": tenant_id,
        })

    return item


def remove_workbox_item(
    *,
    tenant_id: int,
    source_entity: str,
    source_id: str,
) -> int:
    """Remove workbox items matching the given source. Returns count deleted."""
    from simorgh.apps.events.bus import dispatch

    items = WorkItem.objects.filter(
        tenant_id=tenant_id,
        source_entity=source_entity,
        source_id=source_id,
    )
    deleted = 0
    for item in items:
        dispatch("workbox.item_removed", {
            "item_id": str(item.public_id),
            "user_id": item.assigned_to_user_id,
            "tenant_id": tenant_id,
        })
        item.delete()
        deleted += 1
    return deleted


def remove_completed_workbox_items(*, tenant_id: int, user_id: int) -> int:
    """Remove all completed items for a user. Returns count deleted."""
    items = WorkItem.objects.filter(
        tenant_id=tenant_id,
        assigned_to_user_id=user_id,
        status=WorkboxItemStatus.COMPLETED,
    )
    count = items.count()
    items.delete()
    return count


def refresh_user_workbox(
    *,
    tenant_id: int,
    organization_node_id: int,
    user_id: int,
) -> dict[str, int]:
    """Refresh the workbox for a specific user by pulling data from all
    source services. Returns counts of items created/updated per source."""
    stats: dict[str, int] = {}

    stats.update(_refresh_from_assignments(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        user_id=user_id,
    ))
    stats.update(_refresh_from_approvals(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        user_id=user_id,
    ))
    stats.update(_refresh_from_tasks(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        user_id=user_id,
    ))
    stats.update(_refresh_from_workflow(
        tenant_id=tenant_id,
        organization_node_id=organization_node_id,
        user_id=user_id,
    ))

    from simorgh.apps.events.bus import dispatch
    total = sum(stats.values())
    dispatch("workbox.refreshed", {
        "user_id": user_id,
        "tenant_id": tenant_id,
        "item_count": total,
    })
    return stats


def _refresh_from_assignments(
    *, tenant_id: int, organization_node_id: int, user_id: int
) -> dict[str, int]:
    """Pull pending/in-progress assignments into workbox."""
    from simorgh.apps.assignments.models import Assignment

    count = 0
    assignments = Assignment.objects.filter(
        tenant_id=tenant_id,
        assigned_to_id=user_id,
        status__in=("pending", "in_progress"),
    )
    for assignment in assignments:
        status = WorkboxItemStatus.OVERDUE if (
            assignment.due_date and assignment.due_date < timezone.now()
        ) else (
            WorkboxItemStatus.IN_PROGRESS if assignment.status == "in_progress"
            else WorkboxItemStatus.PENDING
        )
        upsert_workbox_item(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            user_id=user_id,
            item_type=WorkboxItemType.ASSIGNMENT,
            source_entity="assignments.assignment",
            source_id=str(assignment.public_id),
            title=assignment.title,
            description=assignment.description,
            status=status,
            priority=assignment.priority,
            source_url=f"/assignments/{assignment.public_id}/",
            due_date=assignment.due_date,
            is_delegated=False,
        )
        count += 1

    # Remove completed/cancelled assignments from workbox
    completed_ids = Assignment.objects.filter(
        tenant_id=tenant_id,
        assigned_to_id=user_id,
        status__in=("completed", "cancelled"),
    ).values_list("public_id", flat=True)
    for public_id in completed_ids:
        remove_workbox_item(
            tenant_id=tenant_id,
            source_entity="assignments.assignment",
            source_id=str(public_id),
        )

    return {"assignments": count}


def _refresh_from_approvals(
    *, tenant_id: int, organization_node_id: int, user_id: int
) -> dict[str, int]:
    """Pull pending approval steps into workbox."""
    from simorgh.apps.approval_engine.models import ApprovalStep

    count = 0
    steps = ApprovalStep.objects.filter(
        request__tenant_id=tenant_id,
        approver_id=user_id,
        status__in=("pending", "in_progress"),
    ).select_related("request")

    for step in steps:
        request = step.request
        status = WorkboxItemStatus.PENDING
        if step.due_date and step.due_date < timezone.now():
            status = WorkboxItemStatus.OVERDUE
        elif step.status == "in_progress":
            status = WorkboxItemStatus.IN_PROGRESS

        upsert_workbox_item(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            user_id=user_id,
            item_type=WorkboxItemType.APPROVAL,
            source_entity="approval_engine.step",
            source_id=str(step.public_id),
            title=request.title if request else "Approval Step",
            description=step.note or "",
            status=status,
            priority=getattr(request, "priority", "medium"),
            source_url=f"/approvals/{request.public_id}/" if request else "",
            due_date=step.due_date,
            completed_at=step.decided_at,
            is_delegated=False,
        )
        count += 1

    # Remove decided steps
    decided_ids = ApprovalStep.objects.filter(
        request__tenant_id=tenant_id,
        approver_id=user_id,
        status__in=("approved", "rejected", "returned", "skipped", "expired"),
    ).values_list("public_id", flat=True)
    for public_id in decided_ids:
        remove_workbox_item(
            tenant_id=tenant_id,
            source_entity="approval_engine.step",
            source_id=str(public_id),
        )

    return {"approvals": count}


def _refresh_from_tasks(
    *, tenant_id: int, organization_node_id: int, user_id: int
) -> dict[str, int]:
    """Pull open tasks into workbox."""
    from simorgh.apps.tasks.models import Task

    count = 0
    tasks = Task.objects.filter(
        tenant_id=tenant_id,
        assignee_id=user_id,
        status__in=("todo", "in_progress", "in_review"),
    )
    for task in tasks:
        status_map = {
            "todo": WorkboxItemStatus.PENDING,
            "in_progress": WorkboxItemStatus.IN_PROGRESS,
            "in_review": WorkboxItemStatus.PENDING,
        }
        status = status_map.get(task.status, WorkboxItemStatus.PENDING)
        if task.due_date and task.due_date < timezone.now():
            status = WorkboxItemStatus.OVERDUE

        upsert_workbox_item(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            user_id=user_id,
            item_type=WorkboxItemType.TASK,
            source_entity="tasks.task",
            source_id=str(task.public_id),
            title=task.title,
            description=task.description or "",
            status=status,
            priority=task.priority,
            source_url=f"/tasks/{task.public_id}/",
            due_date=task.due_date,
        )
        count += 1

    # Remove done/cancelled tasks
    completed_ids = Task.objects.filter(
        tenant_id=tenant_id,
        assignee_id=user_id,
        status__in=("done", "cancelled"),
    ).values_list("public_id", flat=True)
    for public_id in completed_ids:
        remove_workbox_item(
            tenant_id=tenant_id,
            source_entity="tasks.task",
            source_id=str(public_id),
        )

    return {"tasks": count}


def _refresh_from_workflow(
    *, tenant_id: int, organization_node_id: int, user_id: int
) -> dict[str, int]:
    """Pull pending workflow instances into workbox."""
    from simorgh.apps.workflow.models import WorkflowInstance

    count = 0
    instances = WorkflowInstance.objects.filter(
        tenant_id=tenant_id,
        status="active",
    )
    for instance in instances:
        if not instance.current_state:
            continue

        upsert_workbox_item(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            user_id=user_id,
            item_type=WorkboxItemType.WORKFLOW_STEP,
            source_entity="workflow.instance",
            source_id=str(instance.public_id),
            title=f"{instance.definition_name} — {instance.current_state}",
            description="",
            status=WorkboxItemStatus.PENDING,
            priority="medium",
            source_url=f"/workflow/instances/{instance.public_id}/",
        )
        count += 1

    return {"workflow": count}


def get_dashboard_counts(
    *, tenant_id: int, user_id: int
) -> dict[str, int]:
    """Return dashboard widget counts for the workbox."""
    base = WorkItem.objects.filter(tenant_id=tenant_id, assigned_to_user_id=user_id)

    pending = base.filter(status=WorkboxItemStatus.PENDING).count()
    in_progress = base.filter(status=WorkboxItemStatus.IN_PROGRESS).count()
    overdue = base.filter(status=WorkboxItemStatus.OVERDUE).count()
    completed = base.filter(status=WorkboxItemStatus.COMPLETED).count()
    cancelled = base.filter(status=WorkboxItemStatus.CANCELLED).count()
    expired = base.filter(status=WorkboxItemStatus.EXPIRED).count()
    delegated = base.filter(is_delegated=True).count()
    today_due = base.filter(
        due_date__date=timezone.now().date(),
    ).count()

    return {
        "pending": pending,
        "in_progress": in_progress,
        "overdue": overdue,
        "completed": completed,
        "cancelled": cancelled,
        "expired": expired,
        "delegated": delegated,
        "today_due": today_due,
        "total": pending + in_progress + overdue + completed,
    }
