# FlightMonitor/data/opensky_live_adapter.py
"""
Adapter for fetching live flight data from the OpenSky Network API using the official opensky-api library.
This adapter polls the API periodically, transforms the raw data into
CanonicalFlightState objects, and puts structured messages into an output queue.
"""
import requests  # La libreria opensky-api usa requests, quindi potremmo dover gestire le sue eccezioni
import time
import threading
from queue import (
    Queue,
    Empty as QueueEmpty,
    Full as QueueFull,
)  # Manteniamo QueueFull per la gestione della coda di output
import random
from typing import Dict, Any, List, Tuple, Optional


# MODIFICATO: Importazioni per la libreria opensky-api
# PERCHÉ: Utilizzeremo la libreria ufficiale per interagire con l'API OpenSky.
# DOVE: Inizio del file, sezione importazioni.
# COME: Aggiunte le importazioni necessarie dalla libreria opensky_api.
from opensky_api import OpenSkyApi

# Dalla documentazione, sembra che la libreria gestisca il rate-limiting internamente
# e sollevi eccezioni di requests per altri problemi. Non vedo eccezioni specifiche
# di OpenSkyApi da importare per la gestione degli errori di base qui.

from . import config as app_config
from ..utils.logger import get_logger
from .common_models import CanonicalFlightState

module_logger = get_logger(__name__)

# --- Adapter Specific Constants ---
PROVIDER_NAME = (
    "OpenSkyNetwork"  # Rimane lo stesso, identifica la fonte per CanonicalFlightState
)
INITIAL_BACKOFF_DELAY_SECONDS = 20.0
MAX_BACKOFF_DELAY_SECONDS = 300.0
BACKOFF_FACTOR = 1.8
MAX_CONSECUTIVE_ERRORS_THRESHOLD = 5

# --- Message Types and Status Codes for Output Queue ---
AdapterMessage = Dict[str, Any]
FlightDataPayload = List[CanonicalFlightState]

MSG_TYPE_FLIGHT_DATA = "flight_data"
MSG_TYPE_ADAPTER_STATUS = "adapter_status"

STATUS_STARTING = "STARTING"
STATUS_FETCHING = "FETCHING"
STATUS_RECOVERED = "RECOVERED"
STATUS_RATE_LIMITED = (
    "RATE_LIMITED"  # Potremmo non usarlo più se la libreria gestisce internamente
)
STATUS_API_ERROR_TEMPORARY = "API_ERROR_TEMPORARY"
STATUS_PERMANENT_FAILURE = "PERMANENT_FAILURE"
STATUS_STOPPING = "STOPPING"
STATUS_STOPPED = "STOPPED"


class OpenSkyLiveAdapter(threading.Thread):
    """
    Polls the OpenSky Network API using the opensky-api library,
    converts data to CanonicalFlightState, and sends structured messages
    (flight data or status updates) to an output queue.
    Implements exponential backoff for API errors not handled by the library.
    """

    def __init__(
        self,
        output_queue: Queue[AdapterMessage],
        bounding_box: Dict[
            str, float
        ],  # Formato: {"lat_min": ..., "lon_min": ..., "lat_max": ..., "lon_max": ...}
        polling_interval: int = app_config.LIVE_POLLING_INTERVAL_SECONDS,
        # api_timeout non è più direttamente usato da noi, la libreria potrebbe averne uno suo o usare quello di requests
        daemon: bool = True,
    ):

        thread_name = f"OpenSkyLibAdapter-bbox-{bounding_box.get('lat_min',0):.1f}"
        super().__init__(daemon=daemon, name=thread_name)

        if output_queue is None:
            module_logger.critical(
                f"{self.name}: Output queue cannot be None during initialization."
            )
            raise ValueError("Output queue must be provided to OpenSkyLiveAdapter.")

        self.output_queue = output_queue
        self.bounding_box_dict = bounding_box  # Il nostro formato standard
        self.base_polling_interval = float(polling_interval)
        self._stop_event = threading.Event()

        self._consecutive_api_errors: int = 0
        self._current_backoff_delay: float = 0.0
        self._in_backoff_mode: bool = False

        # MODIFICATO: Inizializzazione del client opensky-api
        # PERCHÉ: Necessario per utilizzare la libreria.
        # DOVE: Nel metodo __init__.
        # COME: Creata un'istanza di OpenSkyApi.
        try:
            self.api_client = OpenSkyApi()
            module_logger.info(
                f"{self.name}: OpenSkyApi client initialized successfully."
            )
        except Exception as e:
            module_logger.critical(
                f"{self.name}: Failed to initialize OpenSkyApi client: {e}",
                exc_info=True,
            )
            # Potremmo voler sollevare l'eccezione o gestire lo stato in modo che il thread non parta
            raise RuntimeError(f"Failed to initialize OpenSkyApi client: {e}") from e

        module_logger.debug(
            f"{self.name} initialized. BBox (dict): {self.bounding_box_dict}, "
            f"Base Interval: {self.base_polling_interval}s"
        )

    def _generate_mock_flight_state(self, icao_suffix: int) -> CanonicalFlightState:
        """Helper to generate a single mock CanonicalFlightState."""
        now = time.time()
        lat_center = (
            self.bounding_box_dict["lat_min"] + self.bounding_box_dict["lat_max"]
        ) / 2
        lon_center = (
            self.bounding_box_dict["lon_min"] + self.bounding_box_dict["lon_max"]
        ) / 2
        lat_span = self.bounding_box_dict["lat_max"] - self.bounding_box_dict["lat_min"]
        lon_span = self.bounding_box_dict["lon_max"] - self.bounding_box_dict["lon_min"]

        return CanonicalFlightState(
            icao24=f"mock{icao_suffix:02x}",
            callsign=f"MOCK{icao_suffix:02X}",
            origin_country="Mockland",
            timestamp=now,  # Primary timestamp (posizione)
            last_contact_timestamp=now,  # Timestamp ultimo contatto
            latitude=round(
                lat_center + random.uniform(-lat_span / 2.1, lat_span / 2.1), 4
            ),
            longitude=round(
                lon_center + random.uniform(-lon_span / 2.1, lon_span / 2.1), 4
            ),
            baro_altitude_m=random.uniform(1000, 12000),
            geo_altitude_m=random.uniform(1000, 12000)
            + random.uniform(-200, 200),  # Aggiunto geo_altitude fittizio
            on_ground=random.choice([True, False]),
            velocity_mps=random.uniform(50, 250),
            true_track_deg=random.uniform(0, 360),
            vertical_rate_mps=random.uniform(-10, 10),
            squawk=str(random.randint(1000, 7777)),
            spi=random.choice([True, False]),
            position_source=0,  # ADS-B (fittizio)
            raw_data_provider=f"{PROVIDER_NAME}-Mock",
        )

    def _send_status_to_queue(
        self, status_code: str, message: str, details: Optional[Dict] = None
    ):
        status_payload = {
            "type": MSG_TYPE_ADAPTER_STATUS,
            "status_code": status_code,
            "message": f"{self.name}: {message}",
        }
        if details:
            status_payload["details"] = details
        try:
            self.output_queue.put_nowait(status_payload)
            module_logger.debug(
                f"{self.name}: Sent status '{status_code}' to queue. Msg: {message}"
            )
        except QueueFull:
            module_logger.warning(
                f"{self.name}: Output queue full. Could not send status: {status_code}"
            )
        except Exception as e:
            module_logger.error(
                f"{self.name}: Error sending status '{status_code}' to queue: {e}",
                exc_info=True,
            )

    def stop(self):
        module_logger.info(
            f"Stop signal received for {self.name}. Signaling stop event."
        )
        self._stop_event.set()

    # MODIFICATO: Il metodo _parse_state_vector è stato rimosso.
    # PERCHÉ: La libreria opensky-api restituisce oggetti StateVector già parsati.
    # DOVE: L'intero metodo è stato eliminato.
    # COME: Rimozione del codice del metodo.

    # MODIFICATO: Nuovo metodo per convertire StateVector in CanonicalFlightState
    # PERCHÉ: Necessario per mappare i dati dalla libreria al nostro modello canonico.
    # DOVE: Aggiunto come nuovo metodo helper.
    # COME: Implementata la logica di mapping campo per campo.
    def _convert_state_vector_to_canonical(
        self, sv: Any
    ) -> Optional[CanonicalFlightState]:
        """Converts an OpenSkyApi StateVector object to a CanonicalFlightState object."""
        try:
            if sv.icao24 is None:  # icao24 è un campo obbligatorio
                module_logger.warning(f"Skipping StateVector with None icao24: {sv}")
                return None

            # Gestione timestamp: CanonicalFlightState.timestamp è il timestamp della posizione/stato.
            # StateVector ha time_position e last_contact.
            # Diamo priorità a time_position per il nostro timestamp primario se disponibile.
            primary_ts = (
                sv.time_position if sv.time_position is not None else sv.last_contact
            )
            last_contact_ts = sv.last_contact

            if (
                primary_ts is None
            ):  # Se anche last_contact è None, usiamo il tempo corrente
                primary_ts = time.time()
                module_logger.warning(
                    f"ICAO {sv.icao24}: Using current time as primary_timestamp due to missing API timestamps (last_contact={sv.last_contact}, time_position={sv.time_position})."
                )
            if last_contact_ts is None:
                last_contact_ts = primary_ts  # Se last_contact è None, impostalo al primary_ts determinato

            # La libreria dovrebbe già fornire i valori nelle unità corrette (metri, m/s)
            # ma è bene verificarlo nella documentazione della libreria o con test.
            # Ad esempio, velocity è in m/s, altitude in metri.

            return CanonicalFlightState(
                icao24=sv.icao24,
                callsign=(
                    sv.callsign.strip() if sv.callsign else None
                ),  # Assicurati che il callsign sia pulito
                origin_country=sv.origin_country,
                timestamp=float(primary_ts),
                last_contact_timestamp=float(last_contact_ts),
                latitude=sv.latitude,
                longitude=sv.longitude,
                baro_altitude_m=sv.baro_altitude,
                on_ground=sv.on_ground,
                velocity_mps=sv.velocity,
                true_track_deg=sv.true_track,
                vertical_rate_mps=sv.vertical_rate,
                geo_altitude_m=sv.geo_altitude,
                squawk=sv.squawk,
                spi=sv.spi,
                position_source=(
                    str(sv.position_source) if sv.position_source is not None else None
                ),  # Converti in stringa per coerenza, se necessario
                raw_data_provider=PROVIDER_NAME,
            )
        except AttributeError as e_attr:
            module_logger.error(
                f"AttributeError parsing StateVector: {e_attr}. Vector: {sv}",
                exc_info=False,
            )
            return None
        except Exception as e:
            module_logger.error(
                f"Error converting StateVector for ICAO '{getattr(sv, 'icao24', 'UNKNOWN')}': {e}. Vector: {sv}",
                exc_info=True,
            )
            return None

    def _perform_api_request(self) -> Dict[str, Any]:
        """
        Performs API request using opensky-api library or generates mock data.
        Returns a structured result dictionary.
        """
        # --- MOCK API Logic (invariato) ---
        if app_config.USE_MOCK_OPENSKY_API:
            # ... (logica mock esistente, non la ripeto per brevità ma è identica a prima) ...
            module_logger.info(
                f"{self.name}: Using MOCK API data as per configuration."
            )
            self._send_status_to_queue(
                STATUS_FETCHING, "Generating mock flight data..."
            )
            if (
                app_config.MOCK_API_ERROR_SIMULATION == "RATE_LIMITED"
            ):  # Simula rate limit
                self._consecutive_api_errors += 1
                self._in_backoff_mode = True
                mock_retry_after = str(
                    getattr(app_config, "MOCK_RETRY_AFTER_SECONDS", 60)
                )
                delay = self._calculate_next_backoff_delay(
                    provided_retry_after=mock_retry_after
                )
                err_msg = f"MOCK: Rate limited. Errors: {self._consecutive_api_errors}. Retrying in {delay:.1f}s."
                module_logger.warning(f"{self.name}: {err_msg}")
                return {
                    "error_type": STATUS_RATE_LIMITED,
                    "delay": delay,
                    "message": err_msg,
                    "consecutive_errors": self._consecutive_api_errors,
                    "status_code": 429,
                }
            if (
                app_config.MOCK_API_ERROR_SIMULATION == "HTTP_ERROR"
            ):  # Simula errore HTTP generico
                self._consecutive_api_errors += 1
                self._in_backoff_mode = True
                delay = self._calculate_next_backoff_delay()
                mock_status_code = getattr(app_config, "MOCK_HTTP_ERROR_STATUS", 500)
                err_msg = f"MOCK: HTTP error {mock_status_code}. Errors: {self._consecutive_api_errors}. Retrying in {delay:.1f}s."
                module_logger.error(f"{self.name}: {err_msg}", exc_info=False)
                return {
                    "error_type": STATUS_API_ERROR_TEMPORARY,
                    "status_code": mock_status_code,
                    "message": err_msg,
                    "delay": delay,
                    "consecutive_errors": self._consecutive_api_errors,
                }

            mock_states: List[CanonicalFlightState] = []
            mock_count = getattr(app_config, "MOCK_API_FLIGHT_COUNT", 5)
            for i in range(mock_count):
                mock_states.append(self._generate_mock_flight_state(i + 1))
            module_logger.info(
                f"{self.name}: Generated {len(mock_states)} mock flight states."
            )
            self._reset_error_state()  # Resetta errori se il mock ha successo
            return {"data": mock_states}
        # --- END MOCK API Logic ---

        # --- REAL API Call Logic (MODIFICATO per usare opensky-api) ---
        if not self.bounding_box_dict:  # Usa il nostro formato standard per il check
            err_msg = "Bounding box not set for REAL API request."
            module_logger.error(f"{self.name}: {err_msg}")
            return {
                "error_type": STATUS_PERMANENT_FAILURE,
                "message": err_msg,
                "status_code": "NO_BBOX",
                "delay": 0.0,
                "consecutive_errors": self._consecutive_api_errors,
            }

        # MODIFICATO: Conversione del BBox al formato richiesto dalla libreria (tupla)
        # PERCHÉ: La libreria opensky-api si aspetta una tupla (min_lat, max_lat, min_lon, max_lon).
        # DOVE: Prima della chiamata API.
        # COME: Creata una tupla dal dizionario self.bounding_box_dict.
        try:
            api_bbox_tuple = (
                self.bounding_box_dict["lat_min"],
                self.bounding_box_dict["lat_max"],
                self.bounding_box_dict["lon_min"],
                self.bounding_box_dict["lon_max"],
            )
        except KeyError as e_key:
            err_msg = f"Missing key in bounding_box_dict: {e_key}"
            module_logger.error(f"{self.name}: {err_msg}")
            return {
                "error_type": STATUS_PERMANENT_FAILURE,
                "message": err_msg,
                "status_code": "INVALID_BBOX_FORMAT",
                "delay": 0.0,
                "consecutive_errors": self._consecutive_api_errors,
            }

        self._send_status_to_queue(
            STATUS_FETCHING, f"Requesting REAL data for bbox (tuple): {api_bbox_tuple}"
        )

        try:
            # MODIFICATO: Chiamata API tramite la libreria opensky-api
            # PERCHÉ: Sostituisce la chiamata HTTP manuale.
            # DOVE: All'interno del blocco try per la chiamata API.
            # COME: Utilizzato self.api_client.get_states().
            states_response = self.api_client.get_states(bbox=api_bbox_tuple)

            # La libreria gestisce il rate limiting internamente, quindi non dovremmo vedere 429 qui
            # a meno che la libreria non lo propaghi, cosa che non sembra fare per default.
            # Se get_states() ritorna, assumiamo che sia andato a buon fine o che il rate limit sia stato gestito.

            self._reset_error_state()  # Chiamata API riuscita (o rate limit gestito dalla libreria)

            canonical_states: List[CanonicalFlightState] = []
            if states_response and states_response.states:
                for sv in states_response.states:
                    canonical_sv = self._convert_state_vector_to_canonical(sv)
                    if canonical_sv:
                        canonical_states.append(canonical_sv)
                module_logger.info(
                    f"{self.name}: Fetched and parsed {len(canonical_states)} flight states via opensky-api."
                )
            else:
                module_logger.info(
                    f"{self.name}: API returned no flight states (states_response or .states is null/empty)."
                )
            return {"data": canonical_states}

        # MODIFICATO: Gestione delle eccezioni
        # PERCHÉ: La libreria opensky-api potrebbe sollevare eccezioni di 'requests' per errori di rete/HTTP.
        # DOVE: Nel blocco except per la chiamata API.
        # COME: Catturate eccezioni generiche di 'requests' e attivata la nostra logica di backoff.
        except (
            requests.exceptions.HTTPError
        ) as http_err:  # Cattura errori HTTP specifici
            self._consecutive_api_errors += 1
            self._in_backoff_mode = True
            delay = (
                self._calculate_next_backoff_delay()
            )  # Non c'è Retry-After header da passare qui
            status_code = http_err.response.status_code if http_err.response else "N/A"
            # Se è 429, la libreria dovrebbe averlo gestito. Se lo vediamo qui, è inaspettato.
            if status_code == 429:
                err_msg = f"UNEXPECTED Rate limit (429) propagated by library. Errors: {self._consecutive_api_errors}. Retrying in {delay:.1f}s."
                module_logger.error(f"{self.name}: {err_msg}")
                return {
                    "error_type": STATUS_RATE_LIMITED,
                    "delay": delay,
                    "message": err_msg,
                    "consecutive_errors": self._consecutive_api_errors,
                    "status_code": 429,
                }
            else:
                err_msg = f"HTTP error {status_code} from opensky-api: {http_err}. Errors: {self._consecutive_api_errors}. Retrying in {delay:.1f}s."
                module_logger.error(
                    f"{self.name}: {err_msg}", exc_info=False
                )  # exc_info=False per non loggare lo stacktrace di requests
                return {
                    "error_type": STATUS_API_ERROR_TEMPORARY,
                    "status_code": status_code,
                    "message": err_msg,
                    "delay": delay,
                    "consecutive_errors": self._consecutive_api_errors,
                }

        except requests.exceptions.Timeout:
            self._consecutive_api_errors += 1
            self._in_backoff_mode = True
            delay = self._calculate_next_backoff_delay()
            err_msg = f"API request timed out (via opensky-api). Errors: {self._consecutive_api_errors}. Retrying in {delay:.1f}s."
            module_logger.error(f"{self.name}: {err_msg}", exc_info=False)
            return {
                "error_type": STATUS_API_ERROR_TEMPORARY,
                "status_code": "TIMEOUT",
                "message": err_msg,
                "delay": delay,
                "consecutive_errors": self._consecutive_api_errors,
            }

        except (
            requests.exceptions.RequestException
        ) as req_err:  # Cattura altre eccezioni di requests (es. ConnectionError)
            self._consecutive_api_errors += 1
            self._in_backoff_mode = True
            delay = self._calculate_next_backoff_delay()
            err_msg = f"Request error via opensky-api: {req_err}. Errors: {self._consecutive_api_errors}. Retrying in {delay:.1f}s."
            module_logger.error(
                f"{self.name}: {err_msg}", exc_info=True
            )  # exc_info=True qui può essere utile
            return {
                "error_type": STATUS_API_ERROR_TEMPORARY,
                "status_code": "REQUEST_EXCEPTION",
                "message": err_msg,
                "delay": delay,
                "consecutive_errors": self._consecutive_api_errors,
            }

        except Exception as e:  # Cattura eccezioni impreviste
            self._consecutive_api_errors += 1
            self._in_backoff_mode = True
            delay = self._calculate_next_backoff_delay()
            err_msg = f"Unexpected critical error using opensky-api: {e}. Errors: {self._consecutive_api_errors}. Retrying in {delay:.1f}s."
            module_logger.critical(f"{self.name}: {err_msg}", exc_info=True)
            return {
                "error_type": STATUS_API_ERROR_TEMPORARY,
                "status_code": "UNEXPECTED_LIBRARY_ERROR",
                "message": err_msg,
                "delay": delay,
                "consecutive_errors": self._consecutive_api_errors,
            }

    def _calculate_next_backoff_delay(
        self,
        provided_retry_after: Optional[
            str
        ] = None,  # Mantenuto per il MOCK, ma la libreria dovrebbe gestire per le chiamate reali
    ) -> float:
        calculated_delay = INITIAL_BACKOFF_DELAY_SECONDS * (
            BACKOFF_FACTOR ** (self._consecutive_api_errors - 1)
        )
        api_delay_from_header = 0.0
        # Questa logica per Retry-After potrebbe non essere più necessaria se la libreria gestisce il 429.
        # La manteniamo per il mock e se la libreria dovesse propagare un 429 in modo inaspettato.
        if provided_retry_after is not None and provided_retry_after.isdigit():
            try:
                api_delay_from_header = float(provided_retry_after)
                module_logger.debug(
                    f"{self.name}: Found Retry-After header value: {provided_retry_after}s"
                )
            except ValueError:
                module_logger.warning(
                    f"{self.name}: Could not parse Retry-After header value: '{provided_retry_after}'"
                )
                api_delay_from_header = 0.0

        self._current_backoff_delay = max(api_delay_from_header, calculated_delay)
        self._current_backoff_delay = min(
            self._current_backoff_delay, MAX_BACKOFF_DELAY_SECONDS
        )
        self._current_backoff_delay = max(
            self._current_backoff_delay, 1.0
        )  # Assicura un delay minimo
        module_logger.debug(
            f"{self.name}: Calculated next backoff delay: {self._current_backoff_delay:.1f}s"
        )
        return self._current_backoff_delay

    def _reset_error_state(self):
        if self._in_backoff_mode or self._consecutive_api_errors > 0:
            module_logger.info(
                f"{self.name}: API connection successful after {self._consecutive_api_errors} error(s). Resetting backoff."
            )
            self._send_status_to_queue(STATUS_RECOVERED, "API connection recovered.")
        self._consecutive_api_errors = 0
        self._current_backoff_delay = 0.0
        self._in_backoff_mode = False

    def run(self):
        initial_settle_delay_seconds = (
            0.1  # Breve ritardo per permettere alla GUI di stabilizzarsi se necessario
        )
        module_logger.debug(
            f"{self.name} thread starting initial settle delay ({initial_settle_delay_seconds}s)..."
        )
        if self._stop_event.wait(timeout=initial_settle_delay_seconds):
            module_logger.info(
                f"{self.name} thread received stop signal during initial settle. Terminating."
            )
            return

        module_logger.info(
            f"{self.name} thread is fully operational. Base polling interval: {self.base_polling_interval:.1f}s."
        )
        self._send_status_to_queue(
            STATUS_STARTING, "Adapter thread started, preparing initial fetch."
        )

        while not self._stop_event.is_set():
            if self._consecutive_api_errors >= MAX_CONSECUTIVE_ERRORS_THRESHOLD:
                perm_fail_msg = f"Reached max ({self._consecutive_api_errors}) consecutive API errors. Stopping live updates."
                module_logger.critical(f"{self.name}: {perm_fail_msg}")
                self._send_status_to_queue(STATUS_PERMANENT_FAILURE, perm_fail_msg)
                break  # Esce dal loop principale

            api_result = self._perform_api_request()

            if (
                self._stop_event.is_set()
            ):  # Controlla di nuovo dopo la chiamata API (potrebbe essere lunga)
                module_logger.info(
                    f"{self.name}: Stop event detected after API request. Exiting loop."
                )
                break

            if "data" in api_result:
                flight_data_payload: List[CanonicalFlightState] = api_result["data"]
                try:
                    self.output_queue.put_nowait(
                        {"type": MSG_TYPE_FLIGHT_DATA, "payload": flight_data_payload}
                    )
                    module_logger.debug(
                        f"{self.name}: Sent {len(flight_data_payload)} flight states to queue."
                    )
                except QueueFull:
                    module_logger.warning(
                        f"{self.name}: Output queue full. Discarding {len(flight_data_payload)} flight states."
                    )
                except Exception as e:  # Altre eccezioni mettendo in coda
                    module_logger.error(
                        f"{self.name}: Error putting flight data into queue: {e}",
                        exc_info=True,
                    )

            elif (
                "error_type" in api_result
            ):  # Se _perform_api_request ha restituito un errore strutturato
                error_details_for_controller = api_result.copy()
                # Assicurati che il tipo sia corretto per il messaggio di stato
                error_details_for_controller["type"] = MSG_TYPE_ADAPTER_STATUS
                # status_code è già l'error_type
                # message dovrebbe già essere presente
                # delay e consecutive_errors sono utili per il controller
                self._send_status_to_queue(
                    status_code=api_result[
                        "error_type"
                    ],  # es. STATUS_API_ERROR_TEMPORARY
                    message=api_result.get("message", "An API error occurred."),
                    details=api_result,  # Passa tutti i dettagli dell'errore
                )
            else:  # Struttura risultato sconosciuta
                module_logger.error(
                    f"{self.name}: Unknown result structure from _perform_api_request: {api_result}"
                )

            time_to_wait_seconds: float
            if self._in_backoff_mode:
                time_to_wait_seconds = self._current_backoff_delay
                module_logger.debug(
                    f"{self.name}: In backoff, next attempt in {time_to_wait_seconds:.1f}s."
                )
            else:
                time_to_wait_seconds = self.base_polling_interval
                module_logger.debug(
                    f"{self.name}: Next fetch cycle in {time_to_wait_seconds:.1f}s."
                )

            if time_to_wait_seconds > 0:
                if self._stop_event.wait(timeout=time_to_wait_seconds):
                    print(
                        f"{self.name}: Stop event received during wait period. Exiting loop."
                    )
                    break
            else:  # Se il tempo di attesa è zero o negativo, controlla comunque lo stop event
                if self._stop_event.is_set():
                    print(
                        f"{self.name}: Stop event detected before waiting (wait time <= 0). Exiting loop."
                    )
                    break
        # --- End Main Adapter Loop ---

        print(f"{self.name}: Exited main loop. Sending final STOPPED status.")
        try:
            final_status_payload = {
                "type": MSG_TYPE_ADAPTER_STATUS,
                "status_code": STATUS_STOPPED,
                "message": f"{self.name}: Adapter thread stopped.",
            }
            self.output_queue.put_nowait(final_status_payload)
            print(f"{self.name}: Successfully sent final STATUS_STOPPED to queue.")
        except QueueFull:
            print(
                f"{self.name}: Output queue full. Could not send final STATUS_STOPPED message."
            )
        except Exception as e_final_put:
            print(
                f"{self.name}: Error sending final STATUS_STOPPED message: {e_final_put}",
                exc_info=True,
            )

        print(f"{self.name}: RUN method is terminating now.")
