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

This module provides the Tasks class for handling asynchronous task operations
with the GL Deep Research API, including creating tasks and retrieving results.

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

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

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

from gl_odr.base import BaseAPI
from gl_odr.models import (
    StreamEvent,
    TaskCreateResponse,
    TaskResponse,
    TaskStatusResponse,
    WebhookConfig,
)

logger = logging.getLogger(__name__)


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

    def create(
        self,
        query: str,
        profile: str,
        webhook: WebhookConfig | None = None,
        api_config: dict[str, dict[str, Any]] | None = None,
        mcp_config: dict[str, dict[str, Any]] | None = None,
        extra_headers: dict[str, str] | None = None,
    ) -> TaskCreateResponse:
        """Create an asynchronous deep research task using the specified profile.

        Args:
            query: Query to be researched (minimum length: 1)
            profile: Profile name (e.g., "TONGYI", "GPTR-QUICK", "GPTR-DEEP")
            webhook: Optional webhook configuration with url and secret.
                Webhook will be called when task status changes.
            api_config: Optional per-request API tool overrides, keyed by config ID.
                Example::

                    {
                        "<api_config_id>": {
                            "url": "https://www.fruityvice.com/api/fruit/mango"
                        }
                    }

            mcp_config: Optional per-request MCP tool overrides, keyed by config ID.
                Example::

                    {
                        "217d3ef5-b74f-43b2-9d89-abe4ba554dd3": {
                            "config": {
                                "url": "https://smart-search-dev.catapa.com/mcp",
                                "allowed_tools": ["search_google_calendar"]
                            }
                        }
                    }

            extra_headers: Additional headers

        Returns:
            TaskCreateResponse: Response containing task_id and creation info

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

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

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

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

        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)

        if api_config:
            form_data["api_config"] = json.dumps(api_config)

        if mcp_config:
            form_data["mcp_config"] = json.dumps(mcp_config)

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

    def get(
        self,
        task_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> TaskResponse:
        """Retrieve the full result of an asynchronous deep research task.

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

        Returns:
            TaskResponse: Task details including status and result data

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

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

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

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

    def get_status(
        self,
        task_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> TaskStatusResponse:
        """Get the lightweight status of an asynchronous deep research task.

        This endpoint returns only status information without the full result data.

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

        Returns:
            TaskStatusResponse: Task status information

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

        logger.debug("Getting task status: %s", task_id)

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

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

    def stream(
        self,
        task_id: str,
        extra_headers: dict[str, str] | None = None,
    ) -> Generator[StreamEvent, None, None]:
        """Retrieve streaming events for an asynchronous deep research task.

        This endpoint streams events stored in Redis for the given task ID
        as Server-Sent Events (SSE) in real-time as they become available.

        Events are retained for 24 hours, allowing clients to fetch streaming
        events multiple times during the retention period.

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

        Yields:
            StreamEvent: Streaming events (THINKING_START, THINKING, THINKING_END,
                ACTIVITY, RESPONSE)

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

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

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

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