"""Taskgroups handling for the GL Deep Research Python client.

This module provides the Taskgroups class for handling task group operations
with the GL Deep Research API, including creating groups, adding tasks, and
retrieving results.

Authors:
    Sahat Nicholas Simangunsong (sahat.n.simangunsong@gdplabs.id)

References:
    https://gdplabs.gitbook.io/gl-deepresearch/api-contract/api-taskgroups
"""

import json
import logging
from collections.abc import Generator
from urllib.parse import urljoin

from gl_odr.base import BaseAPI
from gl_odr.models import (
    StreamEvent,
    TaskgroupAddTaskResponse,
    TaskgroupCreateResponse,
    TaskgroupResponse,
    TaskResponse,
    WebhookConfig,
)

logger = logging.getLogger(__name__)


class Taskgroups(BaseAPI):
    """Handles Taskgroups API operations for the GL Deep Research API.

    Taskgroups allow you to:
    - Create multiple tasks with shared configuration (profile and webhook)
    - Track group-level status computed from member tasks
    - Receive webhook notifications for group status changes
    """

    def create(
        self,
        profile: str,
        query: str | list[str] | None = None,
        webhook: WebhookConfig | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> TaskgroupCreateResponse:
        """Create a new taskgroup with one or more research tasks.

        All tasks in the group will share the same profile and webhook configuration.

        Args:
            profile: Profile name (e.g., "TONGYI", "GPTR-QUICK", "GPTR-DEEP")
            query: Query or list of queries.
                - If string: creates one task
                - If list: creates one task per query
                - If None: creates an empty group (no tasks)
            webhook: Optional webhook configuration.
                Webhook will be called when group status changes to STARTED,
                SUCCESS, FAILURE, or PARTIAL_FAILURE.
            extra_headers: Additional headers

        Returns:
            TaskgroupCreateResponse: Response containing taskgroup_id and task IDs

        Raises:
            ValueError: If profile is empty
            httpx.HTTPStatusError: If the API request fails
        """
        if not profile:
            raise ValueError("profile cannot be empty")

        logger.debug("Creating taskgroup with profile: %s", profile)

        url = urljoin(self._client.base_url, "v1/taskgroups")
        headers = self._prepare_headers(extra_headers)
        headers["Content-Type"] = "application/x-www-form-urlencoded"

        form_data: dict[str, str] = {
            "profile": profile,
        }

        if query is not None:
            if isinstance(query, list):
                form_data["query"] = json.dumps(query)
            else:
                form_data["query"] = query

        if webhook:
            webhook_dict = {}
            if webhook.url:
                webhook_dict["url"] = webhook.url
            if webhook.secret:
                webhook_dict["secret"] = webhook.secret
            if webhook_dict:
                form_data["webhook"] = json.dumps(webhook_dict)

        response_data = self._make_request("POST", url, headers, data=form_data)
        return TaskgroupCreateResponse(**response_data)

    def get(
        self,
        taskgroup_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> TaskgroupResponse:
        """Retrieve taskgroup information including computed status.

        The status is calculated from member task statuses:
        - EMPTY: No tasks in group
        - PENDING: At least one task pending, none started
        - STARTED: At least one task started
        - SUCCESS: All tasks completed successfully
        - FAILURE: All tasks failed
        - PARTIAL_FAILURE: Some tasks succeeded, some failed

        Args:
            taskgroup_id: Unique identifier for the taskgroup
            extra_headers: Additional headers

        Returns:
            TaskgroupResponse: Taskgroup details including status and task IDs

        Raises:
            ValueError: If taskgroup_id is empty
            httpx.HTTPStatusError: If the API request fails (404 if not found)
        """
        if not taskgroup_id:
            raise ValueError("taskgroup_id cannot be empty")

        logger.debug("Getting taskgroup: %s", taskgroup_id)

        url = urljoin(self._client.base_url, f"v1/taskgroups/{taskgroup_id}")
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("GET", url, headers)
        return TaskgroupResponse(**response_data)

    def add_task(
        self,
        taskgroup_id: str,
        query: str,
        extra_headers: dict[str, str] | None = None,
    ) -> TaskgroupAddTaskResponse:
        """Add a new task to an existing taskgroup.

        The task inherits the group's profile and webhook configuration.

        Args:
            taskgroup_id: Unique identifier for the taskgroup
            query: The research query for the new task (minimum length: 1)
            extra_headers: Additional headers

        Returns:
            TaskgroupAddTaskResponse: Response containing task_id

        Raises:
            ValueError: If taskgroup_id or query is empty
            httpx.HTTPStatusError: If the API request fails (404 if not found)
        """
        if not taskgroup_id:
            raise ValueError("taskgroup_id cannot be empty")
        if not query:
            raise ValueError("query cannot be empty")

        logger.debug("Adding task to taskgroup: %s", taskgroup_id)

        url = urljoin(self._client.base_url, f"v1/taskgroups/{taskgroup_id}/tasks")
        headers = self._prepare_headers(extra_headers)
        headers["Content-Type"] = "application/x-www-form-urlencoded"

        form_data = {
            "query": query,
        }

        response_data = self._make_request("POST", url, headers, data=form_data)
        return TaskgroupAddTaskResponse(**response_data)

    def get_task(
        self,
        taskgroup_id: str,
        task_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> TaskResponse:
        """Retrieve a specific task from a taskgroup.

        This validates that the task belongs to the specified group.

        Args:
            taskgroup_id: Unique identifier for the taskgroup
            task_id: Unique identifier for the task
            extra_headers: Additional headers

        Returns:
            TaskResponse: Task details including status and result data

        Raises:
            ValueError: If taskgroup_id or task_id is empty
            httpx.HTTPStatusError: If the API request fails (404 if not found)
        """
        if not taskgroup_id:
            raise ValueError("taskgroup_id cannot be empty")
        if not task_id:
            raise ValueError("task_id cannot be empty")

        logger.debug("Getting task %s from taskgroup: %s", task_id, taskgroup_id)

        url = urljoin(
            self._client.base_url, f"v1/taskgroups/{taskgroup_id}/tasks/{task_id}"
        )
        headers = self._prepare_headers(extra_headers)

        response_data = self._make_request("GET", url, headers)
        return TaskResponse(**response_data)

    def stream(
        self,
        taskgroup_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> Generator[StreamEvent, None, None]:
        """Get compiled streaming events from all tasks in a taskgroup.

        Events are streamed sequentially from each task in the group
        using Server-Sent Events (SSE).

        Args:
            taskgroup_id: Unique identifier for the taskgroup
            extra_headers: Additional headers

        Yields:
            StreamEvent: Streaming events from all tasks in the group

        Raises:
            ValueError: If taskgroup_id is empty
            httpx.HTTPStatusError: If the API request fails (404 if not found)
        """
        if not taskgroup_id:
            raise ValueError("taskgroup_id cannot be empty")

        logger.debug("Getting taskgroup stream: %s", taskgroup_id)

        url = urljoin(self._client.base_url, f"v1/taskgroups/{taskgroup_id}/stream")
        headers = self._prepare_headers(extra_headers)

        yield from self._stream_sse("GET", url, headers)

    def stream_task(
        self,
        taskgroup_id: str,
        task_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> Generator[StreamEvent, None, None]:
        """Get streaming events for a specific task in a taskgroup.

        This validates that the task belongs to the specified group.

        Args:
            taskgroup_id: Unique identifier for the taskgroup
            task_id: Unique identifier for the task
            extra_headers: Additional headers

        Yields:
            StreamEvent: Streaming events for the specified task

        Raises:
            ValueError: If taskgroup_id or task_id is empty
            httpx.HTTPStatusError: If the API request fails (404 if not found)
        """
        if not taskgroup_id:
            raise ValueError("taskgroup_id cannot be empty")
        if not task_id:
            raise ValueError("task_id cannot be empty")

        logger.debug("Getting task stream %s from taskgroup: %s", task_id, taskgroup_id)

        url = urljoin(
            self._client.base_url,
            f"v1/taskgroups/{taskgroup_id}/tasks/{task_id}/stream",
        )
        headers = self._prepare_headers(extra_headers)

        yield from self._stream_sse("GET", url, headers)
