# -*- coding: utf-8 -*-
"""
Version Detector - Houdini Engine API version detection
Supports version detection and feature queries for Houdini 19.5 to 21+

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

import logging
from typing import Tuple, Optional, Dict, Any
from dataclasses import dataclass
from functools import lru_cache

logger = logging.getLogger(__name__)


class UnsupportedVersionError(Exception):
    """Raised when Houdini version is below the minimum supported version"""
    pass


class FeatureNotAvailableError(Exception):
    """Raised when the requested feature is not available in the current version"""
    pass


@dataclass
class VersionInfo:
    """Houdini version information"""
    major: int
    minor: int
    build: int
    patch: int = 0
    
    def __str__(self) -> str:
        return f"{self.major}.{self.minor}.{self.build}.{self.patch}"
    
    def __repr__(self) -> str:
        return f"VersionInfo({self.major}, {self.minor}, {self.build}, {self.patch})"
    
    def as_tuple(self) -> Tuple[int, int, int, int]:
        """Return version as tuple"""
        return (self.major, self.minor, self.build, self.patch)
    
    def is_at_least(self, major: int, minor: int = 0, build: int = 0) -> bool:
        """Check if version is at least the specified version"""
        return (self.major, self.minor, self.build) >= (major, minor, build)
    
    def is_below(self, major: int, minor: int = 0, build: int = 0) -> bool:
        """Check if version is below the specified version"""
        return (self.major, self.minor, self.build) < (major, minor, build)


# Minimum supported version
MIN_SUPPORTED_VERSION = (19, 5, 0)
# Maximum tested version
MAX_TESTED_VERSION = (21, 5, 0)

# Global version info cache
_cached_version: Optional[VersionInfo] = None


def _query_hapi_version() -> Optional[VersionInfo]:
    """
    Query version information from the HAPI library
    
    Returns:
        VersionInfo or None if query fails
    """
    try:
        # Lazy import to avoid circular import issues
        import pyhapi.hapi as HAPI
        
        # Check if HAPI library is loaded
        if not hasattr(HAPI, 'HAPI_LIB') or HAPI.HAPI_LIB is None:
            logger.warning("HAPI library not loaded yet")
            return None
        
        # Try to get version info
        # HAPI_GetEnvInt can query HAPI_ENVINT_VERSION_*
        major = HAPI.c_int()
        minor = HAPI.c_int()
        build = HAPI.c_int()
        patch = HAPI.c_int()
        
        # Define HAPI_EnvIntType enum values (based on HAPI docs)
        HAPI_ENVINT_VERSION_HOUDINI_MAJOR = 0
        HAPI_ENVINT_VERSION_HOUDINI_MINOR = 1
        HAPI_ENVINT_VERSION_HOUDINI_BUILD = 2
        HAPI_ENVINT_VERSION_HOUDINI_PATCH = 3
        
        # Query each version component
        try:
            result = HAPI.HAPI_GetEnvInt(
                HAPI_ENVINT_VERSION_HOUDINI_MAJOR,
                HAPI.byref(major)
            )
            if result != HAPI.HAPI_RESULT_SUCCESS:
                logger.warning(f"Failed to query HAPI major version: {result}")
                return None
            
            HAPI.HAPI_GetEnvInt(
                HAPI_ENVINT_VERSION_HOUDINI_MINOR,
                HAPI.byref(minor)
            )
            HAPI.HAPI_GetEnvInt(
                HAPI_ENVINT_VERSION_HOUDINI_BUILD,
                HAPI.byref(build)
            )
            HAPI.HAPI_GetEnvInt(
                HAPI_ENVINT_VERSION_HOUDINI_PATCH,
                HAPI.byref(patch)
            )
            
            version = VersionInfo(
                major=major.value,
                minor=minor.value,
                build=build.value,
                patch=patch.value
            )
            
            logger.info(f"Detected Houdini version: {version}")
            return version
            
        except AttributeError as e:
            # HAPI_GetEnvInt may not exist (older versions)
            logger.warning(f"HAPI_GetEnvInt not available: {e}")
            return None
            
    except Exception as e:
        logger.error(f"Error querying HAPI version: {e}", exc_info=True)
        return None


def get_houdini_version() -> Tuple[int, int, int, int]:
    """
    Get current Houdini Engine version
    
    Returns:
        (major, minor, build, patch) version tuple
        
    Raises:
        UnsupportedVersionError: If version is below the minimum supported version
    """
    version_info = get_version_info()
    return version_info.as_tuple()


@lru_cache(maxsize=1)
def get_version_info() -> VersionInfo:
    """
    Get Houdini version info object (with cache)
    
    Returns:
        VersionInfo object
        
    Raises:
        UnsupportedVersionError: If version is below the minimum supported version
    """
    global _cached_version
    
    if _cached_version is not None:
        return _cached_version
    
    # Try to query version
    version = _query_hapi_version()
    
    if version is None:
        # Query failed, assume minimum supported version
        logger.warning(
            f"Failed to query Houdini version, assuming minimum supported version "
            f"{MIN_SUPPORTED_VERSION[0]}.{MIN_SUPPORTED_VERSION[1]}.{MIN_SUPPORTED_VERSION[2]}"
        )
        version = VersionInfo(*MIN_SUPPORTED_VERSION, 0)
    
    # Check if version is supported
    if version.is_below(*MIN_SUPPORTED_VERSION):
        raise UnsupportedVersionError(
            f"Houdini version {version} is not supported. "
            f"Minimum required version is {MIN_SUPPORTED_VERSION[0]}.{MIN_SUPPORTED_VERSION[1]}.{MIN_SUPPORTED_VERSION[2]}"
        )
    
    # Check if above tested version
    if version.is_at_least(*MAX_TESTED_VERSION):
        logger.warning(
            f"Houdini version {version} is higher than the maximum tested version "
            f"{MAX_TESTED_VERSION[0]}.{MAX_TESTED_VERSION[1]}.{MAX_TESTED_VERSION[2]}. "
            f"Attempting forward compatibility..."
        )
    
    _cached_version = version
    return version


# Feature requirements map: defines the minimum version required for each feature
FEATURE_REQUIREMENTS: Dict[str, Tuple[int, int, int]] = {
    # Session management features
    'session_info': (20, 0, 0),  # HAPI_SessionInfo struct
    'shared_memory_session': (20, 0, 0),  # Shared memory session
    
    # Input node features
    'input_node_parent_id': (20, 0, 0),  # CreateInputNode supports parent_node_id
    
    # Async API features (HAPI 8.0+)
    'async_attributes': (21, 0, 0),  # Async attribute operations
    'async_get_attribute_int_data': (21, 0, 0),
    'async_get_attribute_float_data': (21, 0, 0),
    'async_get_attribute_string_data': (21, 0, 0),
    
    # COPernicus support (HAPI 8.0+)
    'copernicus': (21, 0, 0),  # COP image-related API
    'cop_image_data': (21, 0, 0),
    
    # PDG/TOPs features
    'pdg_tops': (19, 5, 0),  # PDG/TOPs basic support
    
    # Material and texture features
    'material_info': (19, 5, 0),
    'texture_info': (19, 5, 0),
    
    # Geometry features
    'heightfield': (19, 5, 0),
    'volume': (19, 5, 0),
    'instancer': (19, 5, 0),
    
    # Curve features
    'curve_info': (19, 5, 0),
    'nurbs_curve': (19, 5, 0),
}


def has_feature(feature_name: str) -> bool:
    """
    Check if the current Houdini version supports the specified feature
    
    Args:
        feature_name: Feature name (see FEATURE_REQUIREMENTS)
        
    Returns:
        True if supported, False if not supported
        
    Example:
        >>> if has_feature('async_attributes'):
        >>>     # Use async API
        >>>     pass
        >>> else:
        >>>     # Fall back to sync API
        >>>     pass
    """
    if feature_name not in FEATURE_REQUIREMENTS:
        logger.warning(f"Unknown feature: {feature_name}")
        return False
    
    required_version = FEATURE_REQUIREMENTS[feature_name]
    current_version = get_version_info()
    
    return current_version.is_at_least(*required_version)


def require_feature(feature_name: str) -> None:
    """
    Require the current version to support the specified feature, raise exception otherwise
    
    Args:
        feature_name: Feature name
        
    Raises:
        FeatureNotAvailableError: If the feature is not available
    """
    if not has_feature(feature_name):
        required_version = FEATURE_REQUIREMENTS.get(feature_name, (0, 0, 0))
        current_version = get_version_info()
        
        raise FeatureNotAvailableError(
            f"Feature '{feature_name}' is not available in Houdini {current_version}. "
            f"Minimum required version: {required_version[0]}.{required_version[1]}.{required_version[2]}"
        )


def get_diagnostic_info() -> Dict[str, Any]:
    """
    Get complete diagnostic information for issue reporting
    
    Returns:
        Dictionary containing version, feature support and other information
    """
    try:
        version = get_version_info()
        version_str = str(version)
        version_tuple = version.as_tuple()
    except Exception as e:
        version_str = f"Error: {e}"
        version_tuple = (0, 0, 0, 0)
    
    # Check all feature support status
    features = {}
    for feature_name in FEATURE_REQUIREMENTS:
        try:
            features[feature_name] = has_feature(feature_name)
        except Exception:
            features[feature_name] = False
    
    return {
        'version_string': version_str,
        'version_tuple': version_tuple,
        'min_supported_version': MIN_SUPPORTED_VERSION,
        'max_tested_version': MAX_TESTED_VERSION,
        'features': features,
    }


def reset_version_cache() -> None:
    """Reset version cache (mainly for testing)"""
    global _cached_version
    _cached_version = None
    get_version_info.cache_clear()
