"""
SwisperStudio SDK Public Interface

This module defines the typed public API for SDK consumers.
All imports from swisper_studio_sdk should resolve to these exports.

Reference: docs/plans/plan_technical_cleanup_v1.md (Phase P1)

Usage:
    from swisper_studio_sdk import (
        # Zero-config initialization (RECOMMENDED)
        auto_trace,
        
        # Manual initialization
        safe_initialize,
        
        # Graph tracing
        create_traced_graph,
        traced,
        
        # LangGraph patching
        patch_langgraph,
        unpatch_langgraph,
        set_trace_name,
        
        # Wrappers
        wrap_llm_adapter,
        wrap_tools,
        
        # Status checks
        is_sdk_available,
        is_tracing_initialized,
    )
"""

from typing import Type, TypeVar, Dict, Callable, Optional
from langgraph.graph import StateGraph

from ._types import (
    InitializationStatus,
    AutoTraceStatus,
    TracingConfig,
    ObservationType,
    T,
    F,
)

# Re-export types for consumers
__all__ = [
    # Types
    "InitializationStatus",
    "AutoTraceStatus",
    "TracingConfig",
    "ObservationType",
    
    # Zero-config initialization
    "auto_trace",
    
    # Manual initialization
    "safe_initialize",
    "initialize_redis_publisher",
    "close_redis_publisher",
    "disable_tracing",
    
    # Graph tracing
    "create_traced_graph",
    "traced",
    
    # LangGraph patching
    "patch_langgraph",
    "unpatch_langgraph",
    "set_trace_name",
    
    # Wrappers
    "wrap_llm_adapter",
    "wrap_tools",
    
    # Status checks
    "is_sdk_available",
    "is_tracing_initialized",
    
    # Constants
    "DEFAULT_TIMEOUT",
]

# =============================================================================
# ZERO-CONFIG INITIALIZATION
# =============================================================================

TState = TypeVar('TState')


async def auto_trace() -> AutoTraceStatus:
    """
    ONE-LINE SDK initialization with automatic LangGraph patching.
    
    CRITICAL: This function NEVER raises exceptions.
    Even if Redis is down or SDK has bugs, Swisper continues normally.
    
    This function:
    1. Reads config from environment variables
    2. Validates required settings
    3. Initializes Redis connection
    4. Auto-patches StateGraph for transparent tracing
    5. Wraps LLM adapters for complete prompt capture
    
    Environment Variables:
        SWISPER_STUDIO_ENABLED      - "true" to enable (default: "false")
        SWISPER_STUDIO_REDIS_URL    - Redis connection URL (required if enabled)
        SWISPER_STUDIO_PROJECT_ID   - SwisperStudio project ID (required if enabled)
        SWISPER_STUDIO_STREAM_NAME  - Redis stream name (default: "observability:events")
        SWISPER_STUDIO_ENVIRONMENT  - Environment tag, e.g., "production" (optional)
    
    Returns:
        Status dict - NEVER raises exceptions
    
    Example:
        from swisper_studio_sdk import auto_trace
        
        status = await auto_trace()
        logger.info(f"SwisperStudio: {'enabled' if status['initialized'] else 'disabled'}")
    """
    import os
    import logging
    
    logger = logging.getLogger(__name__)
    
    # Default status - everything disabled
    status: AutoTraceStatus = {
        'enabled': False,
        'initialized': False,
        'langgraph_patched': False,
        'llm_wrapped': False,
        'error': None,
        'environment': None,
    }
    
    try:
        # Step 1: Check if tracing is enabled
        enabled_str = os.environ.get('SWISPER_STUDIO_ENABLED', 'false').lower()
        if enabled_str not in ('true', '1', 'yes'):
            status['error'] = 'disabled_by_config'
            return status
        
        status['enabled'] = True
        
        # Step 2: Get environment tag (optional)
        environment = os.environ.get('SWISPER_STUDIO_ENVIRONMENT', '')
        if environment:
            status['environment'] = environment
        
        # Step 3: Validate required config
        redis_url = os.environ.get('SWISPER_STUDIO_REDIS_URL', '')
        project_id = os.environ.get('SWISPER_STUDIO_PROJECT_ID', '')
        stream_name = os.environ.get('SWISPER_STUDIO_STREAM_NAME', 'observability:events')
        
        if not redis_url:
            status['error'] = 'missing_redis_url'
            return status
        
        if not project_id:
            status['error'] = 'missing_project_id'
            return status
        
        # Step 4: Initialize Redis publisher via safe_initialize
        from .tracing.redis_publisher import (
            initialize_redis_publisher,
            is_tracing_initialized,
        )
        
        init_result = await safe_initialize(
            redis_url=redis_url,
            project_id=project_id,
            stream_name=stream_name,
            enabled=True,
        )
        
        if not init_result.get('initialized'):
            status['error'] = init_result.get('error', 'initialization_failed')
            return status
        
        status['initialized'] = True
        
        # Step 5: Auto-patch LangGraph StateGraph
        try:
            from .tracing.langgraph_patch import patch_langgraph
            patch_result = patch_langgraph()
            status['langgraph_patched'] = bool(patch_result)
        except Exception as e:
            logger.warning(f"LangGraph patching failed: {e}")
            status['langgraph_patched'] = False
        
        # Step 6: Wrap LLM adapter (optional - for prompt capture)
        try:
            from .wrappers import wrap_llm_adapter
            wrap_llm_adapter()
            status['llm_wrapped'] = True
        except Exception as e:
            logger.warning(f"LLM wrapper failed: {e}")
            status['llm_wrapped'] = False
        
        return status
        
    except Exception as e:
        # CRITICAL: Never crash Swisper - log and return error status
        logger.warning(f"auto_trace() unexpected error: {e}")
        status['error'] = str(e)
        return status


# =============================================================================
# MANUAL INITIALIZATION
# =============================================================================


async def safe_initialize(
    redis_url: str,
    project_id: str,
    stream_name: str = "observability:events",
    max_stream_length: int = 100000,
    verify_consumer: bool = True,
    connection_timeout: float = 5.0,
    enabled: bool = True,
) -> InitializationStatus:
    """
    SAFELY initialize SwisperStudio SDK tracing.
    
    This function NEVER raises exceptions and NEVER blocks indefinitely.
    If initialization fails for any reason, tracing is silently disabled
    and Swisper continues to operate normally.
    
    This is the RECOMMENDED way to initialize the SDK in production.
    
    Args:
        redis_url: Redis connection URL (e.g., "redis://redis:6379")
        project_id: SwisperStudio project ID
        stream_name: Redis stream name for events
        max_stream_length: Max events to keep in stream
        verify_consumer: Whether to verify consumer is running
        connection_timeout: Max time to wait for Redis connection
        enabled: Whether to enable tracing (False = skip initialization)
    
    Returns:
        Status dict - NEVER raises exceptions
    
    Example:
        status = await safe_initialize(
            redis_url=os.getenv("REDIS_URL", "redis://localhost:6379"),
            project_id=os.getenv("SWISPER_STUDIO_PROJECT_ID", ""),
        )
        
        if status["initialized"]:
            logger.info("✅ SwisperStudio tracing enabled")
    """
    import logging
    
    logger = logging.getLogger(__name__)
    
    # Default status
    status: InitializationStatus = {
        'initialized': False,
        'redis': False,
        'write': False,
        'consumer': False,
        'error': None,
    }
    
    try:
        # If disabled, return early
        if not enabled:
            status['error'] = 'disabled_by_config'
            return status
        
        # Call the actual Redis publisher initialization
        from .tracing.redis_publisher import initialize_redis_publisher
        
        result = await initialize_redis_publisher(
            redis_url=redis_url,
            stream_name=stream_name,
            project_id=project_id,
            max_stream_length=max_stream_length,
            verify_consumer=verify_consumer,
            connection_timeout=connection_timeout,
        )
        
        # Copy results
        status['redis'] = result.get('redis', False)
        status['write'] = result.get('write', False)
        status['consumer'] = result.get('consumer', False)
        status['initialized'] = result.get('initialized', False)
        
        if not status['initialized']:
            status['error'] = 'redis_initialization_failed'
        
        return status
        
    except Exception as e:
        # CRITICAL: Never crash Swisper
        logger.warning(f"safe_initialize() unexpected error: {e}")
        status['error'] = str(e)
        return status


# Import and re-export from internal modules
from .tracing.redis_publisher import (
    initialize_redis_publisher,
    close_redis_publisher,
    disable_tracing,
    is_tracing_initialized,
    DEFAULT_TIMEOUT,
)


# =============================================================================
# GRAPH TRACING
# =============================================================================


def create_traced_graph(
    state_class: Type[TState],
    trace_name: str,
    auto_trace_all_nodes: bool = True,
) -> StateGraph:
    """
    Create a StateGraph with automatic node tracing.
    
    This is the key integration feature - enables tracing with minimal code changes.
    
    ROBUST: If tracing setup fails, returns a normal StateGraph.
    The SDK will NEVER block or crash Swisper.
    
    Args:
        state_class: The LangGraph state class
        trace_name: Name for traces created by this graph
        auto_trace_all_nodes: If True, automatically wraps all nodes with @traced
    
    Returns:
        StateGraph instance with tracing enabled (or plain StateGraph on error)
    
    Example:
        graph = create_traced_graph(GlobalSupervisorState, trace_name="supervisor")
        graph.add_node("intent_classification", intent_classification_node)
        # intent_classification_node is automatically traced!
    """
    from .tracing.graph_wrapper import create_traced_graph as _create_traced_graph
    return _create_traced_graph(state_class, trace_name, auto_trace_all_nodes)


def traced(
    name: Optional[str] = None,
    observation_type: str = "AUTO",
    capture_reasoning: bool = True,
    reasoning_max_length: Optional[int] = None,
) -> Callable[[F], F]:
    """
    Auto-trace any function/node with optional reasoning capture.
    
    Usage:
        @traced("classify_intent")
        async def classify_intent_node(state):
            return state
        
        @traced("memory_node", capture_reasoning=False)
        async def memory_node(state):
            return state
    
    Args:
        name: Observation name (defaults to function name)
        observation_type: Type of observation (SPAN, GENERATION, TOOL, AGENT, AUTO)
                         AUTO = Auto-detect based on captured LLM data
        capture_reasoning: Whether to capture LLM reasoning for this node
        reasoning_max_length: Max reasoning characters (None = use default 50KB)
    
    Returns:
        Decorator function
    """
    from .tracing.decorator import traced as _traced
    return _traced(
        name=name,
        observation_type=observation_type,
        capture_reasoning=capture_reasoning,
        reasoning_max_length=reasoning_max_length,
    )


# =============================================================================
# LANGGRAPH PATCHING
# =============================================================================

from .tracing.langgraph_patch import (
    patch_langgraph,
    unpatch_langgraph,
    set_trace_name,
)


# =============================================================================
# WRAPPERS
# =============================================================================

from .wrappers import wrap_llm_adapter, wrap_tools


# =============================================================================
# STATUS CHECKS
# =============================================================================


def is_sdk_available() -> bool:
    """
    Check if SDK is initialized and ready.
    
    Returns:
        True if tracing is active, False otherwise
    """
    return is_tracing_initialized()
