# radar_data_reader/core/struct_parser.py

import ctypes
from typing import Optional
import numpy as np

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

log = logger.get_logger(__name__)


def _parse_ge_header_block(
    block_data_bytes: bytes, block_name: str
) -> Optional["ds.DspHeaderIn"]:
    GE_HEADER_START_OFFSET_BYTES = 44
    GE_HEADER_SIZE = ctypes.sizeof(ds.GeHeader)
    if len(block_data_bytes) < GE_HEADER_START_OFFSET_BYTES + GE_HEADER_SIZE:
        log.warning(
            f"Block '{block_name}' is too small for GeHeader. Size: {len(block_data_bytes)}, Required: {GE_HEADER_START_OFFSET_BYTES + GE_HEADER_SIZE}"
        )
        return None
    try:
        parsed_header = ds.DspHeaderIn(
            block_name=block_name,
            block_size_words=len(block_data_bytes) // 4,
            ge_header=ds.GeHeader.from_buffer_copy(
                block_data_bytes, GE_HEADER_START_OFFSET_BYTES
            ),
        )
        return parsed_header
    except (ValueError, TypeError) as e:
        log.error(
            f"Failed to map data to GeHeader for block '{block_name}': {e}",
            exc_info=True,
        )
        return None


def _parse_cdpsts_block(
    block_data_bytes: bytes, block_name: str, block_size_words: int
) -> "ds.CdpStsBlock":
    PAYLOAD_START_OFFSET_BYTES = 144
    required_size = PAYLOAD_START_OFFSET_BYTES + ctypes.sizeof(ds.CdpStsPayload)
    if len(block_data_bytes) < required_size:
        return ds.CdpStsBlock(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )
    try:
        payload_struct = ds.CdpStsPayload.from_buffer_copy(
            block_data_bytes, PAYLOAD_START_OFFSET_BYTES
        )
        is_valid = (
            payload_struct.mem_header.marker_low == 0x5A5AA5A5
            and payload_struct.mem_header.marker_high == 0x12345678
        )
        return ds.CdpStsBlock(
            block_name=block_name,
            block_size_words=block_size_words,
            is_valid=is_valid,
            payload=payload_struct,
        )
    except Exception:
        return ds.CdpStsBlock(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )


def _parse_stt_block(
    block_data_bytes: bytes,
    block_name: str,
    block_size_words: int,
    payload_offset_bytes: int,
) -> "ds.SttBlock":
    """Parses a block containing LocalAATrackingData with a dynamic offset."""
    # USA IL PARAMETRO RICEVUTO, NON UN VALORE FISSO
    required_size = payload_offset_bytes + ctypes.sizeof(ds.LocalAATrackingData)

    if len(block_data_bytes) < required_size:
        log.warning(
            f"Block '{block_name}' is too small for LocalAATrackingData. Size: {len(block_data_bytes)}, Required: {required_size}"
        )
        return ds.SttBlock(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )

    try:
        # USA IL PARAMETRO RICEVUTO ANCHE QUI
        tracking_data = ds.LocalAATrackingData.from_buffer_copy(
            block_data_bytes, payload_offset_bytes
        )
        return ds.SttBlock(
            block_name=block_name,
            block_size_words=block_size_words,
            is_valid=True,
            tracking_data=tracking_data,
        )
    except (ValueError, TypeError) as e:
        log.error(
            f"Failed to map data to LocalAATrackingData for block '{block_name}': {e}",
            exc_info=True,
        )
        return ds.SttBlock(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )


def _parse_timer_block(
    block_data_bytes: bytes, block_name: str, block_size_words: int
) -> "ds.TimerBlock":
    full_struct_size = ctypes.sizeof(ds.GrifoTimerBlob)
    mappable_size = min(len(block_data_bytes), full_struct_size)
    header_size = ctypes.sizeof(ds.GrifoFwBlobHeader)
    if len(block_data_bytes) < header_size:
        return ds.TimerBlock(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )
    try:
        timer_blob = ds.GrifoTimerBlob()
        ctypes.memmove(ctypes.addressof(timer_blob), block_data_bytes, mappable_size)
        is_valid = (
            timer_blob.hdr.header_marker == 0x12345678
            and timer_blob.hdr.sub_marker == 0x54494D45
        )
        return ds.TimerBlock(
            block_name=block_name,
            block_size_words=block_size_words,
            is_valid=is_valid,
            blob=timer_blob,
        )
    except Exception:
        return ds.TimerBlock(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )


def _parse_avionics_payload(
    block_data_bytes: bytes, block_name: str, block_size_words: int
) -> "ds.D1553Block":
    AVIONICS_PAYLOAD_OFFSET_BYTES = 36 * 4
    required_size = AVIONICS_PAYLOAD_OFFSET_BYTES + ctypes.sizeof(ds.D1553Payload)
    if len(block_data_bytes) < required_size:
        return ds.D1553Block(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )
    try:
        payload = ds.D1553Payload.from_buffer_copy(
            block_data_bytes, AVIONICS_PAYLOAD_OFFSET_BYTES
        )
        return ds.D1553Block(
            block_name=block_name,
            block_size_words=block_size_words,
            is_valid=True,
            payload=payload,
        )
    except Exception:
        return ds.D1553Block(
            block_name=block_name, block_size_words=block_size_words, is_valid=False
        )


def _parse_aesa_block(
    block_data_bytes: bytes, block_name: str, block_size_words: int
) -> "ds.AesaBlock":
    actual_size = len(block_data_bytes)
    block_subtype = "UNKNOWN_RAW"
    payload = None
    is_valid = True
    SYNTH_REPORT_SIZE, TX_RAW_BLOCK_SIZE, RX_RAW_BLOCK_SIZE = 256, 8424, 4840

    if actual_size == SYNTH_REPORT_SIZE:
        block_subtype = "SYNTHETIC"
        try:
            payload = ds.AesaSyntheticReport.from_buffer_copy(block_data_bytes)
        except Exception:
            is_valid = False
    elif actual_size == TX_RAW_BLOCK_SIZE:
        block_subtype = "TX_RAW"
    elif actual_size == RX_RAW_BLOCK_SIZE:
        block_subtype = "RX_RAW"
    else:
        is_valid = False

    return ds.AesaBlock(
        block_name=block_name,
        block_subtype=block_subtype,
        is_valid=is_valid,
        payload=payload,
        raw_data_bytes=block_data_bytes,
        block_size_words=block_size_words,
    )


def _parse_d1553_block(
    block_data_bytes: bytes, block_name: str, block_size_words: int
) -> "ds.D1553Block":
    return _parse_avionics_payload(block_data_bytes, block_name, block_size_words)


def _parse_firmware_block(
    block_data_bytes: bytes,
    block_name: str,
    block_size_words: int,
    struct_type,
    base_settings_type,
) -> "ds.BaseBlock":
    payload = None
    is_valid = False
    if len(block_data_bytes) >= ctypes.sizeof(base_settings_type):
        try:
            payload = struct_type()
            mappable_size = min(len(block_data_bytes), ctypes.sizeof(struct_type))
            ctypes.memmove(ctypes.addressof(payload), block_data_bytes, mappable_size)
            is_valid = True
        except Exception:
            is_valid = False

    block_class_name = f"{block_name.capitalize()}Block"
    block_class = getattr(ds, block_class_name, ds.GenericBlock)
    return block_class(
        block_name=block_name,
        block_size_words=block_size_words,
        is_valid=is_valid,
        payload=payload,
        raw_data_bytes=block_data_bytes,
    )


def _parse_dsps_block(
    block_data_bytes: bytes,
    block_name: str,
    block_size_words: int,
    last_header: Optional["ds.DspHeaderIn"],
) -> "ds.DspsBlock":
    """Parses a DSPS block, associating it with the last known header."""
    # For now, we treat the payload as raw bytes.
    # If the structure of DSPS payload is known, we can parse it here.
    payload_bytes = block_data_bytes

    # The key is to associate the last header.
    header_struct = last_header.ge_header if last_header else None

    return ds.DspsBlock(
        block_name=block_name,
        block_size_words=block_size_words,
        is_valid=True,  # Assuming valid if parsed
        payload=payload_bytes,
        ge_header=header_struct,
    )


def _parse_signal_block(

    block_data: np.ndarray,
    block_size_words: int,
    signal_type: str,
    last_header: Optional["ds.DspHeaderIn"],
) -> "ds.SignalBlock":
    empty_block = ds.SignalBlock(
        block_name=signal_type,
        block_size_words=block_size_words,
        signal_type=signal_type,
        iq_data=np.array([]),
    )
    if not last_header:
        return empty_block

    n_rbin = last_header.ge_header.signal.packet_descr.nrbin
    n_pri = last_header.ge_header.signal.packet_descr.npri
    if n_rbin <= 0 or n_pri <= 0:
        return empty_block

    marker_indices = np.where(block_data == ds.SIGNAL_DATA_MARKER)[0]
    if not marker_indices.size:
        return empty_block

    signal_start_word = marker_indices[0] + 2
    num_words_for_iq = ((n_rbin * n_pri * 2) + 1) // 2
    if signal_start_word + num_words_for_iq > len(block_data):
        return empty_block

    raw_signal_words = block_data[
        signal_start_word : signal_start_word + num_words_for_iq
    ]
    iq_samples = raw_signal_words.view(np.int16)

    if iq_samples.size % 2 != 0:
        iq_samples = iq_samples[:-1]

    complex_signal = iq_samples[::2].astype(np.float32) + 1j * iq_samples[1::2].astype(
        np.float32
    )
    if complex_signal.size != n_rbin * n_pri:
        return empty_block

    return ds.SignalBlock(
        block_name=signal_type,
        block_size_words=block_size_words,
        signal_type=signal_type,
        iq_data=complex_signal.reshape((n_rbin, n_pri)),
    )


def parse_block(
    block_id: int,
    block_data_numpy: np.ndarray,
    last_header: Optional["ds.DspHeaderIn"],
    block_name_override: Optional[str] = None,
) -> Optional["ds.BaseBlock"]:
    block_name = block_name_override or ds.BLOCK_TYPE_MAP.get(
        block_id, f"UNKNOWN_{block_id}"
    )
    block_size_words = len(block_data_numpy)
    block_data_bytes = block_data_numpy.tobytes()

    try:
        # --- LOGICA DI PARSING MIGLIORATA ---

        # Gestisce blocchi che condividono lo stesso ID primario
        if block_id == 1213223748:  # Potenziale "DSPHDRIN" o "DSPHDROUT"
            # Un DSPHDROUT ha un discriminatore specifico alla parola 18
            if block_size_words > 18 and block_data_numpy[18] == 1431261764:
                # È un DSPHDROUT, contiene dati di tracking
                return _parse_stt_block(
                    block_data_bytes, "DSPHDROUT", block_size_words, payload_offset_bytes=144
                )
            # Altrimenti, è un header standard
            return _parse_ge_header_block(block_data_bytes, "DSPHDRIN")

        # Gestisce i blocchi di tracking LAA
        elif block_id == 1599362131:  # "STT_LAA_NAME"
            if block_size_words > 18 and block_data_numpy[18] == 4276556:
                # L'offset è maggiore a causa di campi spare
                return _parse_stt_block(
                    block_data_bytes, "STT_LAA", block_size_words, payload_offset_bytes=152
                )

        elif block_id == 1599362125:  # "MTT_LAA_NAME"
            if block_size_words > 18 and block_data_numpy[18] == 4276556:
                # Attualmente non parsato in dettaglio, gestito come generico
                return ds.GenericBlock(
                    block_name="MTT_LAA", block_size_words=block_size_words
                )
        
        # Gli altri blocchi rimangono gestiti come prima
        # Gli altri blocchi rimangono gestiti come prima
        elif block_name == "DSPS":
            return _parse_dsps_block(block_data_bytes, block_name, block_size_words, last_header)
        elif block_name == "CDPSTS":
            return _parse_cdpsts_block(block_data_bytes, block_name, block_size_words)
        elif block_name == "TIMER":
            return _parse_timer_block(block_data_bytes, block_name, block_size_words)
        elif block_name == "AESA":
            return _parse_aesa_block(block_data_bytes, block_name, block_size_words)
        elif block_name == "D1553":
            return _parse_d1553_block(block_data_bytes, block_name, block_size_words)
        elif block_name == "SOFT":
            if block_size_words > 18:
                marker1, marker2 = block_data_numpy[17], block_data_numpy[18]
                if marker1 == 0x35353144 and marker2 == 0x00000033:
                    return _parse_avionics_payload(
                        block_data_bytes, "D1553_from_SOFT", block_size_words
                    )
            return ds.GenericBlock(
                block_name=block_name, block_size_words=block_size_words
            )
        elif block_name == "EXP":
            return _parse_firmware_block(
                block_data_bytes, "Exp", block_size_words, ds.ExpRawIf, ds.ExpanderBasicSettings
            )
        elif block_name == "PC":
            return _parse_firmware_block(
                block_data_bytes, "Pc", block_size_words, ds.PcRawIf, ds.PcBasicSettings
            )
        elif block_name == "DET":
            return _parse_firmware_block(
                block_data_bytes, "Det", block_size_words, ds.DetectorRawIf, ds.DetectorBasicSettings
            )
        elif block_name in ["SUM", "GUARD", "DAZ", "DEL", "MTIFFT"]:
            return _parse_signal_block(
                block_data_numpy, block_size_words, block_name, last_header
            )
        else:
            # Fallback per qualsiasi altro blocco non gestito esplicitamente
            return ds.GenericBlock(
                block_name=block_name, block_size_words=block_size_words
            )
            
    except Exception as e:
        log.error(
            f"Unhandled error in parse_block for '{block_name}': {e}", exc_info=True
        )
        return None