"""Types for the task model.

This model is very sensible because serialization logic must be consistent
across all other languages and the core service. `TaskInput` and `TaskOutput`
are strings and are expected to be serialized and deserialized by the user."""

import uuid
from datetime import datetime, timedelta
from enum import Enum
from typing import Optional
from uuid import UUID

from pydantic import BaseModel, Field

TaskInput = str
""" Task input data defined by the user - they can use whatever format they 
want, but they must handle the serialization and deserialization of the data
themselves."""

TaskOutput = str
""" Task output data defined by the user - they can use whatever format they
want, but they must handle the serialization and deserialization of the data 
themselves. """


class TaskStatus(str, Enum):
    """The current status of a task."""

    PENDING = "pending"
    """ The task is created but hasn't yet been received by a worker. """

    PROCESSING = "processing"
    """ The task is being processed by a worker. """

    COMPLETED = "completed"
    """ The task has completed. This does not imply the task was successful. """


class Task(BaseModel):
    """Task to be executed by a worker.

    For simplicity's sake, task objects carry the entire payload of the task, from
    the input data to the output data. You can consult `has_finished` and `status`
    to determine the state of the task.

    ### Attributes:
    - id: The unique ID of the task.
    - task_kind: The kind of the task - dictates which function the worker will execute
    to handle it.
    - worker_kind: The kind of worker that will execute the task. Dictates the queue that
    the task will be routed through.
    - created_at: The time the task was created at.
    - started_at: The time the task was started at.
    - completed_at: The time the task was completed at.
    - input_data: The input data of the task.
    - output_data: The data output by the task.
    - is_error: Whether the task failed. Used primarly for the dead letter queue.
    - status: The current status of the task at the time of retrieval. See `TaskStatus` for more details.
    - priority: The priority of the task, ranging from 0 (lowest) to 255 (highest). For best practices on using the priority, see RabbitMQ's.
    - ttl_duration: An optional determining for how long a task should stay alive after it has been completed.
    - otel_ctx_carrier: The OpenTelemetry context carrier for the task.

    ### Usage:
    Tasks are not meant to be instantiated by the user. They are instead
    created and retrieved using the `PublisherClient`.
    """

    id: UUID = Field(default_factory=uuid.uuid4)
    """The unique ID of the task. Generated by the client so that it can be 
    communicated to the relay and the workers directly."""

    task_kind: str
    """ The kind of the task - dictates which function the worker will execute 
    to handle it."""

    worker_kind: str
    """ The kind of worker that will execute the task. Dictates the queue that 
    the task will be routed through. """

    # Timestamps

    created_at: datetime = Field(default_factory=datetime.now)
    """ The time the task was created at. """

    started_at: Optional[datetime] = Field(default=None)
    """ The time the task was started at. """

    completed_at: Optional[datetime] = Field(default=None)
    """ The time the task was completed at. """

    # Data

    input_data: TaskInput = Field(default="")
    """ The input data of the task."""

    output_data: Optional[TaskOutput] = Field(default=None)
    """ The data output by the task."""

    is_error: int = Field(default=0)
    """ Whether the task failed. Used primarly for the dead letter queue."""

    status: TaskStatus = Field(default=TaskStatus.PENDING)
    """ The current status of the task at the time of retrieval. 
    See `TaskStatus` for more details."""

    # Metadata

    priority: int = Field(default=0)
    """ The priority of the task, ranging from 0 (lowest) to 255 (highest). 
    For best practices on using the priority, see RabbitMQ's 
    [Priority Queues](https://www.rabbitmq.com/priority.html).
    """

    ttl_duration: int = Field(default=60 * 60 * 24 * 7)  # 7 days
    """ The duration of how long the task should live after it has been completed"""

    # Telemetry

    otel_ctx_carrier: Optional[dict[str, str]] = Field(default=None)
    """ The OpenTelemetry context carrier for the task. """

    @property
    def has_finished(self) -> bool:
        """Whether or not the task has finished executing.

        ### Returns:
        `True` if the task has finished, `False` otherwise.
        """

        return self.status == TaskStatus.COMPLETED

    @property
    def completion_time(self) -> Optional[timedelta]:
        """The time it took for the task to complete.

        ### Returns:
        The time it took for the task to complete, or `None` if the task has not completed.
        """

        if self.completed_at is None or self.started_at is None:
            return None
        return self.completed_at - self.started_at
