# -*- coding: utf-8 -*-
"""
Version Adapter - API version compatibility layer
Automatically adapts API calls based on Houdini version

Based on the official HAPI migration guide:
https://www.sidefx.com/docs/hengine/_h_a_p_i__migration.html
"""

import logging
from typing import Optional, Any, Callable
from functools import wraps

from .version_detector import (
    get_version_info,
    has_feature,
    require_feature,
    FeatureNotAvailableError
)

logger = logging.getLogger(__name__)


class APINotAvailableError(Exception):
    """Raised when an API is not available in the current version"""
    pass


class VersionAdapter:
    """
    Base class for version adapter

    Provides version-aware API adaptation
    """
    
    def __init__(self):
        self.version = get_version_info()
        logger.debug(f"Initialized VersionAdapter for Houdini {self.version}")
    
    # ========== Session Management Adaptation ==========
    
    def create_thrift_named_pipe_session(self, session, pipe_name: str):
        """
        Create Thrift Named Pipe session (version-adapted)

        HAPI 7.0+ change: requires HAPI_SessionInfo parameter

        Args:
            session: HAPI_Session object
            pipe_name: pipe name

        Returns:
            HAPI_Result
        """
        import pyhapi.hapi as HAPI
        
        if self.version.is_at_least(20, 0):
            # Houdini 20.0+: requires SessionInfo
            if has_feature('session_info'):
                try:
                    # Create SessionInfo struct
                    session_info = HAPI.HAPI_SessionInfo()
                    session_info.type = HAPI.HAPI_SESSIONTYPE_THRIFT
                    session_info.id = 0
                    
                    result = HAPI.HAPI_CreateThriftNamedPipeSession(
                        HAPI.byref(session),
                        pipe_name.encode('utf-8'),
                        HAPI.byref(session_info)
                    )
                    
                    logger.debug(f"Created Thrift Named Pipe session (v20+): {pipe_name}")
                    return result
                    
                except AttributeError:
                    # Fall back to legacy API
                    logger.warning("HAPI_SessionInfo not available, falling back to legacy API")
        
        # Houdini 19.5: legacy API (no SessionInfo)
        try:
            result = HAPI.HAPI_CreateThriftNamedPipeSession(
                HAPI.byref(session),
                pipe_name.encode('utf-8')
            )
            logger.debug(f"Created Thrift Named Pipe session (legacy): {pipe_name}")
            return result
            
        except TypeError as e:
            # Parameter mismatch
            logger.error(f"Failed to create session: {e}")
            raise APINotAvailableError(
                f"HAPI_CreateThriftNamedPipeSession signature mismatch for version {self.version}"
            )
    
    def create_thrift_socket_session(self, session, host: str, port: int):
        """
        Create Thrift Socket session (version-adapted)

        Args:
            session: HAPI_Session object
            host: host address
            port: port number

        Returns:
            HAPI_Result
        """
        import pyhapi.hapi as HAPI
        
        if self.version.is_at_least(20, 0):
            # Houdini 20.0+: requires SessionInfo
            if has_feature('session_info'):
                try:
                    session_info = HAPI.HAPI_SessionInfo()
                    session_info.type = HAPI.HAPI_SESSIONTYPE_THRIFT
                    session_info.id = 0
                    
                    result = HAPI.HAPI_CreateThriftSocketSession(
                        HAPI.byref(session),
                        host.encode('utf-8'),
                        port,
                        HAPI.byref(session_info)
                    )
                    
                    logger.debug(f"Created Thrift Socket session (v20+): {host}:{port}")
                    return result
                    
                except AttributeError:
                    logger.warning("HAPI_SessionInfo not available, falling back to legacy API")

        # Houdini 19.5: legacy API
        try:
            result = HAPI.HAPI_CreateThriftSocketSession(
                HAPI.byref(session),
                host.encode('utf-8'),
                port
            )
            logger.debug(f"Created Thrift Socket session (legacy): {host}:{port}")
            return result
            
        except TypeError as e:
            logger.error(f"Failed to create session: {e}")
            raise APINotAvailableError(
                f"HAPI_CreateThriftSocketSession signature mismatch for version {self.version}"
            )
    
    # ========== Input Node Adaptation ==========
    
    def create_input_node(self, session, parent_node_id: int = -1, name: str = ""):
        """
        Create input node (version-adapted)

        HAPI change: added parent_node_id parameter

        Args:
            session: HAPI_Session object
            parent_node_id: parent node ID (default -1 means no parent)
            name: node name

        Returns:
            (result, node_id)
        """
        import pyhapi.hapi as HAPI
        
        node_id = HAPI.c_int()
        
        if self.version.is_at_least(20, 0) and has_feature('input_node_parent_id'):
            # Houdini 20.0+: supports parent_node_id
            try:
                result = HAPI.HAPI_CreateInputNode(
                    HAPI.byref(session),
                    parent_node_id,
                    name.encode('utf-8'),
                    HAPI.byref(node_id)
                )
                logger.debug(f"Created input node (v20+): {name}, parent={parent_node_id}")
                return result, node_id.value
                
            except TypeError:
                # Fall back to legacy API
                logger.warning("HAPI_CreateInputNode with parent_node_id failed, falling back")

        # Houdini 19.5: legacy API (no parent_node_id)
        try:
            result = HAPI.HAPI_CreateInputNode(
                HAPI.byref(session),
                name.encode('utf-8'),
                HAPI.byref(node_id)
            )
            logger.debug(f"Created input node (legacy): {name}")
            return result, node_id.value
            
        except TypeError as e:
            logger.error(f"Failed to create input node: {e}")
            raise APINotAvailableError(
                f"HAPI_CreateInputNode signature mismatch for version {self.version}"
            )
    
    def create_input_curve_node(self, session, parent_node_id: int = -1, name: str = ""):
        """
        Create input curve node (version-adapted)

        Args:
            session: HAPI_Session object
            parent_node_id: parent node ID
            name: node name

        Returns:
            (result, node_id)
        """
        import pyhapi.hapi as HAPI
        
        node_id = HAPI.c_int()
        
        if self.version.is_at_least(20, 0) and has_feature('input_node_parent_id'):
            try:
                result = HAPI.HAPI_CreateInputCurveNode(
                    HAPI.byref(session),
                    parent_node_id,
                    name.encode('utf-8'),
                    HAPI.byref(node_id)
                )
                return result, node_id.value
            except TypeError:
                logger.warning("HAPI_CreateInputCurveNode with parent_node_id failed, falling back")

        # Legacy API
        try:
            result = HAPI.HAPI_CreateInputCurveNode(
                HAPI.byref(session),
                name.encode('utf-8'),
                HAPI.byref(node_id)
            )
            return result, node_id.value
        except TypeError as e:
            raise APINotAvailableError(
                f"HAPI_CreateInputCurveNode signature mismatch for version {self.version}"
            )
    
    # ========== Async Attribute API Adaptation ==========
    
    def get_attribute_int_data_async(self, session, node_id: int, part_id: int,
                                      name: str, attr_info, data_array, start: int, length: int):
        """
        Async get integer attribute data (version-adapted)

        Falls back to sync version if async API is not supported

        Args:
            session: HAPI_Session object
            node_id: node ID
            part_id: Part ID
            name: attribute name
            attr_info: HAPI_AttributeInfo object
            data_array: data array
            start: start index
            length: length

        Returns:
            HAPI_Result
        """
        import pyhapi.hapi as HAPI
        
        if self.version.is_at_least(21, 0) and has_feature('async_get_attribute_int_data'):
            try:
                result = HAPI.HAPI_GetAttributeIntDataAsync(
                    HAPI.byref(session),
                    node_id,
                    part_id,
                    name.encode('utf-8'),
                    HAPI.byref(attr_info),
                    -1,  # stride
                    data_array,
                    start,
                    length
                )
                logger.debug(f"Using async API: HAPI_GetAttributeIntDataAsync")
                return result
            except AttributeError:
                logger.info("Async API not available, falling back to sync version")

        # Fall back to sync version
        logger.debug(f"Using sync API: HAPI_GetAttributeIntData")
        return HAPI.HAPI_GetAttributeIntData(
            HAPI.byref(session),
            node_id,
            part_id,
            name.encode('utf-8'),
            HAPI.byref(attr_info),
            -1,
            data_array,
            start,
            length
        )
    
    def get_attribute_float_data_async(self, session, node_id: int, part_id: int,
                                        name: str, attr_info, data_array, start: int, length: int):
        """Async get float attribute data (version-adapted)"""
        import pyhapi.hapi as HAPI
        
        if self.version.is_at_least(21, 0) and has_feature('async_get_attribute_float_data'):
            try:
                return HAPI.HAPI_GetAttributeFloatDataAsync(
                    HAPI.byref(session),
                    node_id,
                    part_id,
                    name.encode('utf-8'),
                    HAPI.byref(attr_info),
                    -1,
                    data_array,
                    start,
                    length
                )
            except AttributeError:
                pass

        # Fall back to sync version
        return HAPI.HAPI_GetAttributeFloatData(
            HAPI.byref(session),
            node_id,
            part_id,
            name.encode('utf-8'),
            HAPI.byref(attr_info),
            -1,
            data_array,
            start,
            length
        )
    
    # ========== COPernicus API Adaptation ==========

    def supports_copernicus(self) -> bool:
        """Check if COPernicus API is supported"""
        return has_feature('copernicus')
    
    # ========== Utility Methods ==========

    def check_api_availability(self, api_name: str) -> bool:
        """
        Check if the specified API is available

        Args:
            api_name: API function name

        Returns:
            True if available
        """
        import pyhapi.hapi as HAPI
        
        try:
            api_func = getattr(HAPI, api_name)
            return callable(api_func)
        except AttributeError:
            return False


# Global adapter instance
_global_adapter: Optional[VersionAdapter] = None


def get_adapter() -> VersionAdapter:
    """
    Get global version adapter instance (singleton)

    Returns:
        VersionAdapter instance
    """
    global _global_adapter
    
    if _global_adapter is None:
        _global_adapter = VersionAdapter()
    
    return _global_adapter


def reset_adapter() -> None:
    """Reset global adapter (mainly for testing)"""
    global _global_adapter
    _global_adapter = None


# ========== Decorator: Version Requirement ==========

def require_version(major: int, minor: int = 0, build: int = 0):
    """
    Decorator: requires the function to be called on the specified version or higher

    Example:
        @require_version(20, 0)
        def use_new_feature():
            pass
    """
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            version = get_version_info()
            if version.is_below(major, minor, build):
                raise FeatureNotAvailableError(
                    f"Function '{func.__name__}' requires Houdini {major}.{minor}.{build} or higher. "
                    f"Current version: {version}"
                )
            return func(*args, **kwargs)
        return wrapper
    return decorator


def fallback_on_error(fallback_func: Callable):
    """
    Decorator: automatically falls back to the backup function when the decorated function fails

    Example:
        @fallback_on_error(sync_version)
        def async_version():
            pass
    """
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            try:
                return func(*args, **kwargs)
            except (AttributeError, TypeError, APINotAvailableError) as e:
                logger.warning(f"Function '{func.__name__}' failed: {e}, falling back")
                return fallback_func(*args, **kwargs)
        return wrapper
    return decorator
