"""Utility functions for creating A2A Task objects."""

import binascii
import uuid

from base64 import b64decode, b64encode
from typing import Literal, Protocol, runtime_checkable

from a2a.types.a2a_pb2 import (
    Artifact,
    Message,
    Task,
    TaskState,
    TaskStatus,
)
from a2a.utils.constants import MAX_LIST_TASKS_PAGE_SIZE
from a2a.utils.errors import InvalidParamsError


def new_task(request: Message) -> Task:
    """Creates a new Task object from an initial user message.

    Generates task and context IDs if not provided in the message.

    Args:
        request: The initial `Message` object from the user.

    Returns:
        A new `Task` object initialized with 'submitted' status and the input message in history.

    Raises:
        TypeError: If the message role is None.
        ValueError: If the message parts are empty, if any part has empty content, or if the provided context_id is invalid.
    """
    if not request.role:
        raise TypeError('Message role cannot be None')
    if not request.parts:
        raise ValueError('Message parts cannot be empty')
    for part in request.parts:
        if part.HasField('text') and not part.text:
            raise ValueError('Message.text cannot be empty')

    return Task(
        status=TaskStatus(state=TaskState.TASK_STATE_SUBMITTED),
        id=request.task_id or str(uuid.uuid4()),
        context_id=request.context_id or str(uuid.uuid4()),
        history=[request],
    )


def completed_task(
    task_id: str,
    context_id: str,
    artifacts: list[Artifact],
    history: list[Message] | None = None,
) -> Task:
    """Creates a Task object in the 'completed' state.

    Useful for constructing a final Task representation when the agent
    finishes and produces artifacts.

    Args:
        task_id: The ID of the task.
        context_id: The context ID of the task.
        artifacts: A list of `Artifact` objects produced by the task.
        history: An optional list of `Message` objects representing the task history.

    Returns:
        A `Task` object with status set to 'completed'.
    """
    if not artifacts or not all(isinstance(a, Artifact) for a in artifacts):
        raise ValueError(
            'artifacts must be a non-empty list of Artifact objects'
        )

    if history is None:
        history = []
    return Task(
        status=TaskStatus(state=TaskState.TASK_STATE_COMPLETED),
        id=task_id,
        context_id=context_id,
        artifacts=artifacts,
        history=history,
    )


@runtime_checkable
class HistoryLengthConfig(Protocol):
    """Protocol for configuration arguments containing history_length field."""

    history_length: int

    def HasField(self, field_name: Literal['history_length']) -> bool:  # noqa: N802 -- Protobuf generated code
        """Checks if a field is set.

        This method name matches the generated Protobuf code.
        """
        ...


def validate_history_length(config: HistoryLengthConfig | None) -> None:
    """Validates that history_length is non-negative."""
    if config and config.history_length < 0:
        raise InvalidParamsError(message='history length must be non-negative')


def apply_history_length(
    task: Task, config: HistoryLengthConfig | None
) -> Task:
    """Applies history_length parameter on task and returns a new task object.

    Args:
        task: The original task object with complete history
        config: Configuration object containing 'history_length' field and HasField method.

    Returns:
        A new task object with limited history

    See Also:
        https://a2a-protocol.org/latest/specification/#324-history-length-semantics
    """
    if config is None or not config.HasField('history_length'):
        return task

    history_length = config.history_length

    if history_length == 0:
        if not task.history:
            return task
        task_copy = Task()
        task_copy.CopyFrom(task)
        task_copy.ClearField('history')
        return task_copy

    if history_length > 0 and task.history:
        if len(task.history) <= history_length:
            return task

        task_copy = Task()
        task_copy.CopyFrom(task)
        del task_copy.history[:-history_length]
        return task_copy

    return task


def validate_page_size(page_size: int) -> None:
    """Validates that page_size is in range [1, 100].

    See Also:
        https://a2a-protocol.org/latest/specification/#314-list-tasks
    """
    if page_size < 1:
        raise InvalidParamsError(message='minimum page size is 1')
    if page_size > MAX_LIST_TASKS_PAGE_SIZE:
        raise InvalidParamsError(
            message=f'maximum page size is {MAX_LIST_TASKS_PAGE_SIZE}'
        )


_ENCODING = 'utf-8'


def encode_page_token(task_id: str) -> str:
    """Encodes page token for tasks pagination.

    Args:
        task_id: The ID of the task.

    Returns:
        The encoded page token.
    """
    return b64encode(task_id.encode(_ENCODING)).decode(_ENCODING)


def decode_page_token(page_token: str) -> str:
    """Decodes page token for tasks pagination.

    Args:
        page_token: The encoded page token.

    Returns:
        The decoded task ID.
    """
    encoded_str = page_token
    missing_padding = len(encoded_str) % 4
    if missing_padding:
        encoded_str += '=' * (4 - missing_padding)
    try:
        decoded = b64decode(encoded_str.encode(_ENCODING)).decode(_ENCODING)
    except (binascii.Error, UnicodeDecodeError) as e:
        raise InvalidParamsError(
            'Token is not a valid base64-encoded cursor.'
        ) from e
    return decoded
