"""AgentBill OpenInference Auto-Instrumentation (P3-E)

Provides `instrument()` and `uninstrument()` for automatic AI provider tracing.

Dual-path strategy:
  1. If OpenInference instrumentor is installed → use it (full coverage)
  2. If not → fall back to legacy monkey-patch from autoinstrument.py

[OPENINFERENCE:P3-E] All code in this module is part of the P3-E implementation.
To find all P3-E code: grep -rn "OPENINFERENCE:P3-E" sdks/python/

Supported providers (OpenInference):
  - openai: openinference-instrumentation-openai
  - anthropic: openinference-instrumentation-anthropic
  - bedrock: openinference-instrumentation-bedrock
  - cohere: openinference-instrumentation-cohere
  - google_generativeai: openinference-instrumentation-google-generativeai
  - langchain: openinference-instrumentation-langchain

Migration:
  - agentbill_autoinstrument() continues to work (delegates here internally)
  - wrap_openai() / wrap_anthropic() unaffected
  - v12.0.0: agentbill_autoinstrument() will emit deprecation warning
  - v13.0.0: agentbill_autoinstrument() removed
"""
import importlib
import os
import warnings
from typing import Dict, List, Optional, Any

from .signals import get_signal_config, set_signal_config

# [OPENINFERENCE:P3-E] Registry of OpenInference instrumentor class names per provider
_OI_REGISTRY: Dict[str, Dict[str, str]] = {
    "openai": {
        "module": "openinference.instrumentation.openai",
        "class": "OpenAIInstrumentor",
    },
    "anthropic": {
        "module": "openinference.instrumentation.anthropic",
        "class": "AnthropicInstrumentor",
    },
    "bedrock": {
        "module": "openinference.instrumentation.bedrock",
        "class": "BedrockInstrumentor",
    },
    "cohere": {
        "module": "openinference.instrumentation.cohere",
        "class": "CohereInstrumentor",
    },
    "google_generativeai": {
        "module": "openinference.instrumentation.google_generativeai",
        "class": "GoogleGenerativeAIInstrumentor",
    },
    "langchain": {
        "module": "openinference.instrumentation.langchain",
        "class": "LangChainInstrumentor",
    },
}

# Alias map for user-friendly provider names
_PROVIDER_ALIASES: Dict[str, str] = {
    "google": "google_generativeai",
    "google_genai": "google_generativeai",
    "gemini": "google_generativeai",
    "aws_bedrock": "bedrock",
}

# [OPENINFERENCE:P3-E] Active instrumentor instances (for uninstrument)
_active_instrumentors: Dict[str, Any] = {}

# [OPENINFERENCE:P3-E] Providers using legacy fallback
_legacy_instrumented: Dict[str, bool] = {}


def _resolve_provider(name: str) -> str:
    """Resolve provider aliases to canonical names."""
    return _PROVIDER_ALIASES.get(name.lower(), name.lower())


def _try_openinference(provider: str, tracer_provider: Any, debug: bool = False) -> bool:
    """[OPENINFERENCE:P3-E] Try to use OpenInference instrumentor for a provider.
    
    Returns True if OI instrumentor was successfully loaded and activated.
    Returns False if OI package is not installed (caller should use legacy fallback).
    """
    info = _OI_REGISTRY.get(provider)
    if not info:
        if debug:
            print(f"[AgentBill P3-E] No OpenInference registry entry for '{provider}'")
        return False

    try:
        mod = importlib.import_module(info["module"])
        cls = getattr(mod, info["class"])
        instrumentor = cls()

        # Configure with our tracer provider if available
        kwargs = {}
        if tracer_provider is not None:
            kwargs["tracer_provider"] = tracer_provider

        instrumentor.instrument(**kwargs)
        _active_instrumentors[provider] = instrumentor

        if debug:
            print(f"[AgentBill P3-E] ✓ OpenInference instrumentor active for '{provider}'")
        return True

    except ImportError:
        if debug:
            print(f"[AgentBill P3-E] OpenInference package not installed for '{provider}', using legacy fallback")
        return False
    except Exception as e:
        if debug:
            print(f"[AgentBill P3-E] OpenInference init failed for '{provider}': {e}, using legacy fallback")
        return False


def _legacy_fallback(provider: str, config: Dict[str, Any], debug: bool = False) -> bool:
    """[OPENINFERENCE:P3-E] [LEGACY_COMPAT:P3-E] Fall back to legacy monkey-patching.
    
    Uses existing _instrument_openai/etc from autoinstrument.py.
    Target removal: v13.0.0 when OI is the only path.
    """
    from . import autoinstrument as _legacy

    legacy_map = {
        "openai": _legacy._instrument_openai,
        "anthropic": _legacy._instrument_anthropic,
        "cohere": _legacy._instrument_cohere,
        "google_generativeai": _legacy._instrument_google,
        "google": _legacy._instrument_google,
    }

    fn = legacy_map.get(provider)
    if fn is None:
        if debug:
            print(f"[AgentBill P3-E] No legacy instrumentor for '{provider}', skipping")
        return False

    try:
        fn(config)
        _legacy_instrumented[provider] = True
        if debug:
            print(f"[AgentBill P3-E] ✓ Legacy monkey-patch active for '{provider}'")
        return True
    except ImportError:
        if debug:
            print(f"[AgentBill P3-E] Provider library '{provider}' not installed, skipping")
        return False
    except Exception as e:
        if debug:
            print(f"[AgentBill P3-E] Legacy fallback failed for '{provider}': {e}")
        return False


def instrument(
    *,
    providers: Optional[List[str]] = None,
    api_key: Optional[str] = None,
    customer_id: Optional[str] = None,
    agent_id: Optional[str] = None,
    base_url: str = "https://api.agentbill.io",
    debug: bool = False,
) -> List[str]:
    """[OPENINFERENCE:P3-E] Auto-detect and instrument AI providers.
    
    Uses OpenInference instrumentors when available, falls back to legacy
    monkey-patching when not.
    
    Args:
        providers: List of providers to instrument. Default: auto-detect all.
                   Options: "openai", "anthropic", "cohere", "google",
                            "bedrock", "langchain", "google_generativeai"
        api_key: AgentBill API key (or uses existing client config / env var)
        customer_id: Default customer ID
        agent_id: Optional agent ID
        base_url: AgentBill backend URL
        debug: Enable debug logging
    
    Returns:
        List of successfully instrumented provider names.
    
    Example:
        >>> import agentbill
        >>> instrumented = agentbill.instrument(providers=["openai", "anthropic"])
        >>> print(instrumented)  # ['openai', 'anthropic']
    """
    # Build config from args or existing signal config
    existing_config = get_signal_config() or {}
    effective_key = api_key or existing_config.get("api_key") or os.environ.get("AGENTBILL_API_KEY")
    
    config = {
        "api_key": effective_key,
        "customer_id": customer_id or existing_config.get("customer_id"),
        "agent_id": agent_id or existing_config.get("agent_id"),
        "base_url": base_url,
        "debug": debug,
    }
    set_signal_config(config)

    # Get tracer provider if available (P3-A native mode)
    tracer_provider = None
    try:
        from .otel_provider import OTEL_AVAILABLE
        if OTEL_AVAILABLE:
            from .otel_provider import create_tracer_provider
            tracer_provider = create_tracer_provider(config)
    except Exception:
        pass

    # Determine target providers
    all_providers = list(_OI_REGISTRY.keys())
    if providers:
        target = [_resolve_provider(p) for p in providers]
    else:
        target = all_providers

    instrumented: List[str] = []
    for provider in target:
        # Skip if already instrumented
        if provider in _active_instrumentors or provider in _legacy_instrumented:
            if debug:
                print(f"[AgentBill P3-E] '{provider}' already instrumented, skipping")
            instrumented.append(provider)
            continue

        # Try OpenInference first, fallback to legacy
        if _try_openinference(provider, tracer_provider, debug):
            instrumented.append(provider)
        elif _legacy_fallback(provider, config, debug):
            instrumented.append(provider)

    if debug:
        print(f"[AgentBill P3-E] Instrumented providers: {instrumented}")

    return instrumented


def uninstrument(providers: Optional[List[str]] = None) -> None:
    """[OPENINFERENCE:P3-E] Uninstrument AI providers (restore originals).
    
    Args:
        providers: Specific providers to uninstrument. Default: all active.
    """
    targets = providers or list(_active_instrumentors.keys()) + list(_legacy_instrumented.keys())
    targets = [_resolve_provider(p) for p in targets]

    for provider in targets:
        # Uninstrument OpenInference
        if provider in _active_instrumentors:
            try:
                _active_instrumentors[provider].uninstrument()
            except Exception:
                pass
            del _active_instrumentors[provider]

        # Clear legacy flag (monkey-patches cannot be cleanly undone)
        if provider in _legacy_instrumented:
            del _legacy_instrumented[provider]


def get_instrumented_providers() -> List[str]:
    """[OPENINFERENCE:P3-E] Get list of currently instrumented providers."""
    oi = list(_active_instrumentors.keys())
    legacy = list(_legacy_instrumented.keys())
    return sorted(set(oi + legacy))
