# Copyright 2024 Klira AI
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Abstract base class for framework adapters.

Every framework adapter (Standard, OpenAI Agents, LangChain, LangGraph,
CrewAI, LlamaIndex) inherits from this class.

Adapters only adapt — core span creation logic stays in decorators.
(Learning #12: Decorated functions must always remain callable.)
(Learning #13: Always disable framework's native telemetry.)
"""

from __future__ import annotations

from abc import ABC, abstractmethod
from typing import Any, Callable


class BaseFrameworkAdapter(ABC):
    """Abstract base for framework adapters.

    Subclasses must implement:
        - adapt_workflow(): Wrap workflow for framework-specific behavior.
        - adapt_agent(): Wrap agent for framework-specific behavior.
        - adapt_tool(): Wrap tool for framework-specific behavior.
        - patch_framework(): Apply patches AND disable native telemetry.
        - verify_suppression(): Confirm native telemetry is disabled.

    Key invariants:
        - adapt_tool() must preserve callability — framework objects are
          stored as metadata, never replace the callable. (Learning #12)
        - patch_framework() must disable the framework's native telemetry
          to prevent duplicate traces. (Learning #13)
    """

    @property
    @abstractmethod
    def framework_name(self) -> str:
        """Return the framework name (e.g., 'langchain', 'crewai')."""

    @abstractmethod
    def adapt_workflow(self, func: Callable[..., Any]) -> Callable[..., Any]:
        """Wrap a workflow function for framework-specific behavior.

        The returned callable must have the same calling convention as
        the original function.

        Args:
            func: The decorated workflow function.
            **kwargs: Decorator arguments.

        Returns:
            A wrapped callable with framework-specific behavior.
        """

    @abstractmethod
    def adapt_agent(self, func: Callable[..., Any]) -> Callable[..., Any]:
        """Wrap an agent function for framework-specific behavior.

        Args:
            func: The decorated agent function.
            **kwargs: Decorator arguments.

        Returns:
            A wrapped callable with framework-specific behavior.
        """

    @abstractmethod
    def adapt_tool(self, func: Callable[..., Any]) -> Callable[..., Any]:
        """Wrap a tool function for framework-specific behavior.

        CRITICAL: The returned value must be callable with the same
        signature as the original function. Framework-specific objects
        (like OpenAI's FunctionTool) must be stored as metadata on the
        function, NOT returned in place of the callable. (Learning #12)

        Args:
            func: The decorated tool function.
            **kwargs: Decorator arguments.

        Returns:
            A wrapped callable (NOT a framework object).
        """

    @abstractmethod
    def adapt_task(self, func: Callable[..., Any]) -> Callable[..., Any]:
        """Wrap a task function for framework-specific behavior.

        Args:
            func: The decorated task function.
            **kwargs: Decorator arguments.

        Returns:
            A wrapped callable with framework-specific behavior.
        """

    @abstractmethod
    def patch_framework(self) -> None:
        """Apply class-level patches to the framework AND disable the
        framework's native telemetry.

        Must call verify_suppression() after patching to confirm native
        telemetry is disabled. (Learning #13)

        Known native telemetry to suppress:
            - OpenAI Agents SDK: set_tracing_disabled(True)
            - LangChain/LangSmith: Disable LANGCHAIN_TRACING_V2
            - CrewAI: Disable Traceloop auto-instrumentation
            - LlamaIndex: Disable callback-based tracing
            - LiteLLM: Disable success_callback/failure_callback

        Raises:
            RuntimeError: If verify_suppression() fails.
        """

    @abstractmethod
    def verify_suppression(self) -> bool:
        """Confirm native telemetry is disabled.

        Returns True if suppression is active. Raises RuntimeError if not.
        """
