"""Abstract conductor interface for model orchestration.

Partners implement this interface to build their own orchestration logic.
The interface is shape-compatible with both local (RND1) and remote (Claude) backends.

Example usage:

    from partner_inference_harness.conductor import ConductorInterface, ConductorRequest

    class MyCustomConductor(ConductorInterface):
        def complete(self, request: ConductorRequest) -> ConductorResponse:
            # Custom orchestration logic here
            ...

        @property
        def backend_type(self) -> str:
            return "custom"

    conductor = MyCustomConductor()
    response = conductor.complete(ConductorRequest(query="What is ML?"))
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from dataclasses import dataclass, field
from typing import Any


@dataclass
class ConductorRequest:
    """Input to conductor orchestration.

    Attributes:
        query: The user's query text.
        context: Optional context or system instructions.
        max_tokens: Maximum tokens to generate.
        temperature: Sampling temperature for generation.
        metadata: Optional metadata for tracking or custom use.
    """

    query: str
    context: str | None = None
    max_tokens: int = 4096
    temperature: float = 0.7
    metadata: dict[str, Any] = field(default_factory=dict)


@dataclass
class ConductorResponse:
    """Output from conductor orchestration.

    Attributes:
        text: The generated response text.
        model_used: Identifier of the model that produced the response.
        latency_ms: End-to-end latency in milliseconds.
        metadata: Additional response metadata (tokens, finish reason, etc.).
    """

    text: str
    model_used: str
    latency_ms: float
    metadata: dict[str, Any] = field(default_factory=dict)


class ConductorInterface(ABC):
    """Abstract interface for conductor implementations.

    Partners implement this to create orchestration logic using either
    local models (RND1 via vLLM/SGLang) or remote APIs (Claude, GPT-4).

    The interface follows a request/response pattern that is agnostic to
    the underlying backend, enabling partners to build custom orchestration
    strategies while maintaining compatibility with the harness infrastructure.

    Attributes:
        backend_type: Property indicating 'local' or 'remote' backend type.
    """

    @abstractmethod
    def complete(self, request: ConductorRequest) -> ConductorResponse:
        """Execute a completion request through the conductor.

        Args:
            request: The completion request containing query and parameters.

        Returns:
            ConductorResponse with generated text and metadata.

        Raises:
            ConductorError: If the completion request fails.
        """
        ...

    @property
    @abstractmethod
    def backend_type(self) -> str:
        """Return 'local' or 'remote' indicating backend type.

        Returns:
            String identifier for backend type.
        """
        ...

    def health_check(self) -> bool:
        """Check if the conductor backend is healthy and reachable.

        Returns:
            True if the backend is healthy, False otherwise.
        """
        return True


class ConductorError(Exception):
    """Base exception for conductor errors.

    Attributes:
        message: Error description.
        backend: The backend that raised the error.
        retriable: Whether the request can be retried.
    """

    def __init__(
        self,
        message: str,
        backend: str | None = None,
        retriable: bool = False,
    ) -> None:
        """Initialize conductor error.

        Args:
            message: Error description.
            backend: The backend that raised the error.
            retriable: Whether the request can be retried.
        """
        super().__init__(message)
        self.message = message
        self.backend = backend
        self.retriable = retriable
