"""
Helper utilities for error capture (Pydantic v1/v2 compatible)
"""

import traceback
import sys
import json
from typing import Dict, Any, Optional, List
from pathlib import Path

# from pydantic.fields import ModelPrivateAttr


def get_frame_info(exception: Exception, skip_frames: int = 0) -> Dict[str, Any]:
    """
    Extract frame information from exception for context.

    Args:
        exception: The exception to analyze
        skip_frames: Number of frames to skip from the top

    Returns:
        Dictionary with function_name, file_path, line_number
    """
    try:
        tb = exception.__traceback__
        if tb is None:
            return {}

        # Skip decorator and wrapper frames
        for _ in range(skip_frames):
            if tb.tb_next:
                tb = tb.tb_next

        frame = tb.tb_frame
        code = frame.f_code

        return {
            "function_name": code.co_name,
            "file_path": str(Path(code.co_filename).resolve()),
            "line_number": tb.tb_lineno,
        }
    except Exception:
        return {}


def format_stack_trace(exception: Exception, max_length: Optional[int] = None) -> str:
    """
    Format exception stack trace as string.

    Args:
        exception: The exception to format
        max_length: Maximum length of stack trace (characters)

    Returns:
        Formatted stack trace string
    """
    try:
        tb_lines = traceback.format_exception(
            type(exception), exception, exception.__traceback__
        )
        stack_trace = "".join(tb_lines)

        if max_length and len(stack_trace) > max_length:
            stack_trace = stack_trace[:max_length] + "\n... (truncated)"

        return stack_trace
    except Exception:
        return f"Unable to format stack trace for {type(exception).__name__}"


def get_exception_hierarchy(exception: Exception) -> List[str]:
    """
    Get the exception class hierarchy.

    Args:
        exception: The exception to analyze

    Returns:
        List of class names in the exception hierarchy
    """
    hierarchy = []
    for cls in type(exception).__mro__:
        if cls is object:
            break
        hierarchy.append(cls.__name__)
    return hierarchy


def sanitize_metadata(metadata: Dict[str, Any], max_size: int = 10000) -> Dict[str, Any]:
    """
    Sanitize metadata by converting to JSON-serializable format.
    
    This approach uses JSON serialization to strip out all non-serializable objects
    including Pydantic private attributes (both v1 and v2).
    
    Always returns a dictionary, never None.
    """
    
    # Handle None or non-dict input
    if metadata is None:
        return {}
    if not isinstance(metadata, dict):
        from ..utils.logger_config import get_logger
        logger = get_logger()
        logger.warning(f"Metadata is not a dict: {type(metadata)}")
        return {}
    
    # Strategy: Convert to JSON and back to filter out all problematic objects
    def make_serializable(obj, depth=0):
        """Recursively make object JSON serializable"""
        # Prevent infinite recursion
        if depth > 10:
            return "<max-depth-reached>"
        
        # Handle None
        if obj is None:
            return None
        
        # Handle primitive types
        if isinstance(obj, (str, int, float, bool)):
            return obj
        
        # Handle lists/tuples
        if isinstance(obj, (list, tuple)):
            try:
                return [make_serializable(item, depth + 1) for item in obj]
            except Exception:
                return []
        
        # Handle dictionaries
        if isinstance(obj, dict):
            result = {}
            try:
                for key, value in obj.items():
                    # Skip private keys (both v1 and v2 patterns)
                    if isinstance(key, str) and key.startswith('_'):
                        continue
                    # Skip __pydantic_private__ (v2) and __fields__ (v1)
                    if key in ("__pydantic_private__", "__fields__", "__fields_set__"):
                        continue
                    try:
                        # Convert key to string if needed
                        str_key = str(key) if not isinstance(key, str) else key
                        result[str_key] = make_serializable(value, depth + 1)
                    except Exception:
                        # Skip problematic key-value pairs
                        continue
            except Exception:
                return {}
            return result
        
        # For any other type, try to convert to string
        try:
            # Try json.dumps to see if it's serializable
            json.dumps(obj)
            return obj
        except (TypeError, ValueError):
            # Not serializable, convert to string representation
            try:
                return f"<{type(obj).__name__}>"
            except Exception:
                return "<unknown>"
    
    try:
        # Make the metadata serializable
        cleaned = make_serializable(metadata)
        
        # Ensure it's a dict
        if not isinstance(cleaned, dict):
            return {}
        
        # Try to serialize to check size
        try:
            serialized = json.dumps(cleaned)
            if len(serialized) <= max_size:
                return cleaned
        except Exception:
            # If serialization still fails, return safe error dict
            return {"error": "Unable to serialize metadata"}
        
        # If too large, truncate
        truncated = {}
        current_size = 0
        for key, value in cleaned.items():
            try:
                value_str = json.dumps(value)
                if current_size + len(value_str) > max_size:
                    truncated[key] = f"<truncated>"
                    break
                else:
                    truncated[key] = value
                    current_size += len(value_str)
            except Exception:
                truncated[key] = "<unserializable>"
        
        return truncated
        
    except Exception as e:
        from ..utils.logger_config import get_logger
        logger = get_logger()
        logger.warning(f"Error during metadata sanitization: {e}")
        return {"error": "Metadata sanitization failed"}


def is_ignored_exception(exception: Exception, ignore_types: List[str]) -> bool:
    """
    Check if exception should be ignored based on type.

    Args:
        exception: The exception to check
        ignore_types: List of exception type names to ignore

    Returns:
        True if exception should be ignored
    """
    exception_type = type(exception).__name__
    return exception_type in ignore_types


def get_caller_info(skip_frames: int = 2) -> Dict[str, Any]:
    """
    Get information about the calling function.

    Args:
        skip_frames: Number of frames to skip

    Returns:
        Dictionary with caller information
    """
    try:
        frame = sys._getframe(skip_frames)
        code = frame.f_code

        return {
            "function_name": code.co_name,
            "file_path": str(Path(code.co_filename).resolve()),
            "line_number": frame.f_lineno,
        }
    except Exception:
        return {}


def truncate_string(text: str, max_length: int, suffix: str = "...") -> str:
    """
    Truncate string to maximum length.

    Args:
        text: The text to truncate
        max_length: Maximum length
        suffix: Suffix to add when truncated

    Returns:
        Truncated string
    """
    if len(text) <= max_length:
        return text
    return text[: max_length - len(suffix)] + suffix


def safe_str(obj: Any, max_length: int = 1000) -> str:
    """
    Safely convert object to string with length limit.

    Args:
        obj: The object to convert
        max_length: Maximum string length

    Returns:
        String representation
    """
    try:
        s = str(obj)
        return truncate_string(s, max_length)
    except Exception:
        return f"<{type(obj).__name__}>"


def merge_metadata(*metadata_dicts: Dict[str, Any]) -> Dict[str, Any]:
    """
    Merge multiple metadata dictionaries safely.

    Args:
        *metadata_dicts: Metadata dictionaries to merge

    Returns:
        Merged metadata dictionary
    """
    result = {}
    for metadata in metadata_dicts:
        if not metadata:
            continue
        
        # Only process if it's a dict
        if not isinstance(metadata, dict):
            continue
        
        try:
            # Safely iterate
            for key in list(metadata.keys()):
                try:
                    # Skip private keys (both v1 and v2 patterns)
                    if isinstance(key, str) and key.startswith('_'):
                        continue
                    # Skip Pydantic internal keys
                    if key in ("__pydantic_private__", "__fields__", "__fields_set__"):
                        continue
                    # Get value safely
                    value = metadata.get(key)
                    if value is not None:
                        result[key] = value
                except Exception:
                    # Skip problematic keys
                    continue
        except Exception:
            # Skip this metadata dict entirely if iteration fails
            continue
    
    return result