# radar_data_reader/core/struct_utils.py

"""
Utilities for safe data extraction from raw data blocks using ctypes structures.
"""
from typing import Optional, Any, Tuple, Type
import numpy as np
import ctypes
import collections.abc
import dataclasses

from . import data_structures as ds
from .data_structures import CtypesStructureBase
from ..utils import logger

log = logger.get_logger(__name__)

# Registry mapping block names to their main payload structure and payload offset (in 32-bit words)
# Questo registro è la chiave per rendere la funzione "atomica".
BLOCK_STRUCTURE_REGISTRY: dict[str, Tuple[Type[ctypes.Structure], int]] = {
    "DSPHDRIN": (ds.GeHeader, 34),
    "DSPS": (ds.GeHeader, 34),
    "CDPSTS": (ds.CdpStsPayload, 36),
    "DSPHDROUT": (ds.LocalAATrackingData, 36),
    "STT_LAA": (ds.LocalAATrackingData, 38), # LAA ha 2 parole spare in più
    "TIMER": (ds.GrifoTimerBlob, 0), # L'header del blob è all'inizio
    # Aggiungere altre strutture se necessario
}

def extract_field_from_block(
    block_data_words: np.ndarray,
    block_name: str,
    field_path: str
) -> Optional[Any]:
    """
    Safely extracts a value from a raw block by mapping it to its ctypes structure.

    Args:
        block_data_words: The raw block data as a numpy array of uint32.
        block_name: The name of the block (e.g., "DSPHDRIN").
        field_path: A dot-separated path to the desired field (e.g., "signal.batch_counter").

    Returns:
        The extracted value, or None if the block is invalid, too short,
        or the field path does not exist.
    """
    if block_name not in BLOCK_STRUCTURE_REGISTRY:
        return None

    structure_class, payload_offset_words = BLOCK_STRUCTURE_REGISTRY[block_name]
    
    # L'offset del payload deve essere calcolato in byte
    payload_offset_bytes = payload_offset_words * 4
    
    # Lavoriamo con i byte
    block_data_bytes = block_data_words.tobytes()
    
    # Controllo di sicurezza: c'è abbastanza spazio per la struttura?
    required_size = payload_offset_bytes + ctypes.sizeof(structure_class)
    if len(block_data_bytes) < required_size:
        log.warning(
            f"Cannot extract field '{field_path}' from '{block_name}': "
            f"block size ({len(block_data_bytes)} B) is smaller than required ({required_size} B)."
        )
        return None
        
    try:
        # Mappa in modo sicuro i dati sulla struttura. from_buffer_copy è essenziale
        # perché non legge oltre la fine del buffer fornito.
        mapped_struct = structure_class.from_buffer_copy(block_data_bytes, payload_offset_bytes)
        
        # Naviga il percorso per trovare il valore
        current_obj = mapped_struct
        for part in field_path.split('.'):
            current_obj = getattr(current_obj, part)
            
        return current_obj
    except (AttributeError, ValueError, TypeError) as e:
        log.error(f"Failed to extract '{field_path}' from '{block_name}': {e}", exc_info=True)
        return None

def convert_ctypes_to_dict(obj: Any) -> Any:
    """
    Recursively converts a ctypes object to a JSON-serializable dictionary.
    Handles nested structures, arrays, and basic ctypes data types.
    """
    if isinstance(obj, (int, float, str, bool)) or obj is None:
        return obj

    if isinstance(obj, bytes):
        try:
            return obj.decode('utf-8', errors='ignore')
        except UnicodeDecodeError:
            return repr(obj)

    if isinstance(obj, (ctypes._SimpleCData, np.number)):
        return obj.value

    if dataclasses.is_dataclass(obj) and not isinstance(obj, type):
        result = {}
        for f in dataclasses.fields(obj):
            value = getattr(obj, f.name)
            result[f.name] = convert_ctypes_to_dict(value)
        return result

    if isinstance(obj, CtypesStructureBase):
        result = {}
        for field_name, _ in obj._fields_:
            if field_name.startswith("_"):
                continue
            value = getattr(obj, field_name)
            result[field_name] = convert_ctypes_to_dict(value)
        return result

    if isinstance(obj, ctypes.Array):
        return [convert_ctypes_to_dict(item) for item in obj]

    if isinstance(obj, np.ndarray):
        return obj.tolist()

    if isinstance(obj, collections.abc.Mapping):
        return {k: convert_ctypes_to_dict(v) for k, v in obj.items()}

    if isinstance(obj, collections.abc.Iterable) and not isinstance(obj, (str, bytes)):
        return [convert_ctypes_to_dict(i) for i in obj]
    
    # Fallback for other types
    try:
        # Attempt to serialize if it's a custom object with a to_dict method or similar
        if hasattr(obj, 'to_dict'):
            return obj.to_dict()
        return str(obj)
    except Exception:
        return repr(obj)