"""Tasks services — business logic.

All write operations go through these functions; views must NOT call ORM
save/delete directly on Tasks models.
"""

from __future__ import annotations

from datetime import date
from decimal import Decimal

from django.db import transaction
from django.utils import timezone


# ---------------------------------------------------------------------------
# TaskTag services
# ---------------------------------------------------------------------------

def create_task_tag(*, tenant_id: int, name: str, color: str = "") -> "TaskTag":
    from simorgh.apps.tasks.models import TaskTag
    return TaskTag.objects.create(tenant_id=tenant_id, name=name.strip(), color=color)


def update_task_tag(tag: "TaskTag", *, name: str | None = None, color: str | None = None) -> "TaskTag":
    if name is not None:
        tag.name = name.strip()
    if color is not None:
        tag.color = color
    tag.save()
    return tag


def delete_task_tag(tag: "TaskTag") -> None:
    tag.delete()


# ---------------------------------------------------------------------------
# Task services
# ---------------------------------------------------------------------------

def create_task(
    *,
    tenant_id: int,
    organization_node_id: int,
    title: str,
    reporter_id: int,
    description: str = "",
    status: str = "todo",
    priority: str = "none",
    project_id: int | None = None,
    phase_id: int | None = None,
    assignee_id: int | None = None,
    due_date: date | None = None,
    estimated_hours: Decimal | None = None,
    parent_id: int | None = None,
    tag_ids: list[int] | None = None,
    order: float = 0.0,
) -> "Task":
    from simorgh.apps.tasks.models import Task, TaskTag
    from simorgh.apps.events.bus import dispatch

    with transaction.atomic():
        task = Task.objects.create(
            tenant_id=tenant_id,
            organization_node_id=organization_node_id,
            title=title.strip(),
            description=description,
            status=status,
            priority=priority,
            project_id=project_id,
            phase_id=phase_id,
            reporter_id=reporter_id,
            assignee_id=assignee_id,
            due_date=due_date,
            estimated_hours=estimated_hours,
            parent_id=parent_id,
            order=order,
        )
        if tag_ids:
            tags = TaskTag.objects.filter(tenant_id=tenant_id, pk__in=tag_ids)
            task.tags.set(tags)

    dispatch("tasks.task.created", {
        "task_id":     str(task.public_id),
        "project_id":  project_id,
        "tenant_id":   tenant_id,
        "assignee_id": assignee_id,
        "reporter_id": reporter_id,
    })
    return task


def update_task(
    task: "Task",
    *,
    title: str | None = None,
    description: str | None = None,
    priority: str | None = None,
    project_id: int | None = ...,   # type: ignore[assignment]
    phase_id: int | None = ...,     # type: ignore[assignment]
    assignee_id: int | None = ...,  # type: ignore[assignment]
    due_date: "date | None" = ...,  # type: ignore[assignment]
    estimated_hours: "Decimal | None" = ...,  # type: ignore[assignment]
    tag_ids: list[int] | None = None,
    order: float | None = None,
    actor_id: int | None = None,
) -> "Task":
    _SENTINEL = ...  # type: ignore[assignment]

    if title is not None:
        task.title = title.strip()
    if description is not None:
        task.description = description
    if priority is not None:
        task.priority = priority
    if project_id is not _SENTINEL:
        task.project_id = project_id  # type: ignore[assignment]
    if phase_id is not _SENTINEL:
        task.phase_id = phase_id  # type: ignore[assignment]
    if assignee_id is not _SENTINEL:
        old_assignee = task.assignee_id
        task.assignee_id = assignee_id  # type: ignore[assignment]
        if assignee_id != old_assignee and assignee_id is not None:
            from simorgh.apps.events.bus import dispatch
            dispatch("tasks.task.assigned", {
                "task_id":     str(task.public_id),
                "assignee_id": assignee_id,
                "tenant_id":   task.tenant_id,
                "actor_id":    actor_id,
            })
    if due_date is not _SENTINEL:
        task.due_date = due_date  # type: ignore[assignment]
    if estimated_hours is not _SENTINEL:
        task.estimated_hours = estimated_hours  # type: ignore[assignment]
    if order is not None:
        task.order = order

    task.save()

    if tag_ids is not None:
        from simorgh.apps.tasks.models import TaskTag
        tags = TaskTag.objects.filter(tenant_id=task.tenant_id, pk__in=tag_ids)
        task.tags.set(tags)

    return task


def delete_task(task: "Task") -> None:
    task.delete()


def change_task_status(task: "Task", *, new_status: str, actor_id: int | None = None) -> "Task":
    from simorgh.apps.tasks.models import TaskStatus, TaskStatusChange
    from simorgh.apps.events.bus import dispatch

    old_status = task.status
    if old_status == new_status:
        return task

    TaskStatusChange.objects.create(
        task=task,
        from_status=old_status,
        to_status=new_status,
        changed_by_id=actor_id,
    )

    task.status = new_status
    task.save(update_fields=["status", "updated_at"])

    dispatch("tasks.task.status_changed", {
        "task_id":     str(task.public_id),
        "from_status": old_status,
        "to_status":   new_status,
        "tenant_id":   task.tenant_id,
        "actor_id":    actor_id,
    })

    if new_status == TaskStatus.DONE:
        dispatch("tasks.task.completed", {
            "task_id":    str(task.public_id),
            "project_id": task.project_id,
            "tenant_id":  task.tenant_id,
            "actor_id":   actor_id,
        })

    return task


def assign_task(task: "Task", *, user_id: int | None, actor_id: int | None = None) -> "Task":
    from simorgh.apps.events.bus import dispatch

    old_assignee = task.assignee_id
    task.assignee_id = user_id
    task.save(update_fields=["assignee_id", "updated_at"])

    if user_id and user_id != old_assignee:
        dispatch("tasks.task.assigned", {
            "task_id":     str(task.public_id),
            "assignee_id": user_id,
            "tenant_id":   task.tenant_id,
            "actor_id":    actor_id,
        })
    return task


# ---------------------------------------------------------------------------
# Comment services
# ---------------------------------------------------------------------------

def add_comment(task: "Task", *, author_id: int, body: str) -> "TaskComment":
    from simorgh.apps.tasks.models import TaskComment
    from simorgh.apps.events.bus import dispatch

    if not body or not body.strip():
        raise ValueError("Comment body cannot be empty.")

    comment = TaskComment.objects.create(
        task=task,
        author_id=author_id,
        body=body.strip(),
    )
    dispatch("tasks.comment.added", {
        "task_id":    str(task.public_id),
        "comment_id": comment.pk,
        "author_id":  author_id,
        "tenant_id":  task.tenant_id,
    })
    return comment


def update_comment(comment: "TaskComment", *, body: str) -> "TaskComment":
    if not body or not body.strip():
        raise ValueError("Comment body cannot be empty.")
    comment.body = body.strip()
    comment.is_edited = True
    comment.save()
    return comment


def delete_comment(comment: "TaskComment") -> None:
    comment.delete()


# ---------------------------------------------------------------------------
# Watcher services
# ---------------------------------------------------------------------------

def watch_task(task: "Task", *, user_id: int) -> "TaskWatcher":
    from simorgh.apps.tasks.models import TaskWatcher
    watcher, _ = TaskWatcher.objects.get_or_create(task=task, user_id=user_id)
    return watcher


def unwatch_task(task: "Task", *, user_id: int) -> None:
    from simorgh.apps.tasks.models import TaskWatcher
    TaskWatcher.objects.filter(task=task, user_id=user_id).delete()


# ---------------------------------------------------------------------------
# Attachment services
# ---------------------------------------------------------------------------

def add_attachment(task: "Task", *, dms_file_id: str, filename: str = "", uploaded_by_id: int) -> "TaskAttachment":
    import uuid as _uuid
    from simorgh.apps.tasks.models import TaskAttachment

    MAX_ATTACHMENTS = 20
    if task.attachments.count() >= MAX_ATTACHMENTS:
        raise ValueError("Maximum 20 attachments per task.")

    return TaskAttachment.objects.create(
        task=task,
        dms_file_id=_uuid.UUID(str(dms_file_id)),
        filename=filename,
        uploaded_by_id=uploaded_by_id,
    )


def delete_attachment(attachment: "TaskAttachment") -> None:
    attachment.delete()
