from __future__ import annotations

import functools
from abc import ABC, abstractmethod
from datetime import datetime
from typing import Any, Dict, Iterable, List, Optional, Tuple

import tzlocal

from api_clients.A2A.constants import AcceptedOutputMode


class BaseAgentClient(ABC):
    """Abstract base class for agent API clients.
    """

    @abstractmethod
    def fetch_available_agents(self) -> List[Dict[str, Any]]:
        """Return the list of agents accessible to the configured user.

        Implementations that do not support agent enumeration should
        return an empty list.
        """
        pass

    @abstractmethod
    def send_prompt(
        self,
        prompt: str,
        agent_id: str | None = None,
        conversation_context: Optional[Dict[str, Any]] = None,
        *,
        accepted_output_modes: Optional[Iterable[AcceptedOutputMode]] = None,
    ) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]]]:
        """Send a single prompt and return the response with conversation context.

        For single-turn usage, pass conversation_context=None.
        For multi-turn usage, pass the context returned from the previous turn.

        Args:
            prompt: The prompt string to send.
            agent_id: Optional agent ID to target.
            conversation_context: Opaque context dict from a previous turn,
                or None for the first turn / single-turn usage.
            accepted_output_modes: A2A media types for
                ``configuration.acceptedOutputModes`` (use constants from
                :mod:`api_clients.A2A.constants`). ``None`` or empty omits
                the configuration block; callers compose this list per-prompt
                so non-retrieval prompts stay on the lighter payload path.

        Returns:
            Tuple of (enhanced_response_dict, conversation_context).
            The conversation_context should be passed to the next turn
            in a multi-turn conversation, or discarded for single-turn.
            The context structure is implementation-specific:
              - A2A: {"context_id": str}
            Returns None as context when no conversation state is established.
        """
        pass

    def resolve_agent(self, agent_id: str) -> None:
        """Pre-resolve agent endpoint. Called once before pipeline starts.

        Default is no-op. Subclasses may override to cache agent discovery.
        """
        pass

    @staticmethod
    @functools.lru_cache(maxsize=1)
    def _get_iana_timezone_name() -> str:
        try:
            return tzlocal.get_localzone_name()
        except Exception:
            return str(tzlocal.get_localzone())

    @staticmethod
    @functools.lru_cache(maxsize=1)
    def _get_location_info() -> Dict[str, Any]:
        now = datetime.now().astimezone()
        utc_offset = now.utcoffset()
        offset_hours = int(utc_offset.total_seconds() // 3600) if utc_offset is not None else 0
        return {
            "timeZoneOffset": offset_hours,
            "timeZone": BaseAgentClient._get_iana_timezone_name(),
        }

