# FlightMonitor/controller/app_controller.py
from queue import Queue, Empty as QueueEmpty
import threading
import tkinter as tk
import time
import os
import csv
import copy
from datetime import datetime, timezone

from ..data.opensky_live_adapter import (
    OpenSkyLiveAdapter,
    AdapterMessage,
    MSG_TYPE_FLIGHT_DATA,
    MSG_TYPE_ADAPTER_STATUS,
    STATUS_STARTING,
    STATUS_FETCHING,
    STATUS_RECOVERED,
    STATUS_RATE_LIMITED,
    STATUS_API_ERROR_TEMPORARY,
    STATUS_PERMANENT_FAILURE,
    STATUS_STOPPED,
)
from ..data import config as app_config
from ..utils.logger import get_logger
from ..data.storage import DataStorage
from ..data.common_models import CanonicalFlightState
from ..data.aircraft_database_manager import AircraftDatabaseManager
from typing import List, Optional, Dict, Any, TYPE_CHECKING, Callable

from ..utils.gui_utils import (
    GUI_STATUS_OK,
    GUI_STATUS_WARNING,
    GUI_STATUS_ERROR,
    GUI_STATUS_FETCHING,
    GUI_STATUS_UNKNOWN,
)

# MODIFIED: Import _is_valid_bbox_dict from map_utils for bounding_box validation
from ..map.map_utils import _is_valid_bbox_dict


if TYPE_CHECKING:
    from ..gui.main_window import MainWindow
    from ..gui.dialogs.import_progress_dialog import ImportProgressDialog
    from ..gui.dialogs.full_flight_details_window import FullFlightDetailsWindow
    from ..map.map_canvas_manager import MapCanvasManager

module_logger = get_logger(__name__)

GUI_QUEUE_CHECK_INTERVAL_MS = 150
ADAPTER_JOIN_TIMEOUT_SECONDS = 5.0
DEFAULT_CLICK_AREA_SIZE_KM = 50.0


class AppController:
    def __init__(self):
        self.main_window: Optional["MainWindow"] = None
        self.live_adapter_thread: Optional[OpenSkyLiveAdapter] = None
        self.is_live_monitoring_active: bool = False
        self.flight_data_queue: Optional[Queue[AdapterMessage]] = None
        self._gui_after_id: Optional[str] = None
        self._active_bounding_box: Optional[Dict[str, float]] = None
        self.data_storage: Optional[DataStorage] = None
        self.aircraft_db_manager: Optional[AircraftDatabaseManager] = None

        self.active_detail_window_icao: Optional[str] = None
        self.active_detail_window_ref: Optional["FullFlightDetailsWindow"] = None

        try:
            self.data_storage = DataStorage()
            module_logger.info("DataStorage initialized successfully by AppController.")
        except Exception as e:
            module_logger.critical(
                f"CRITICAL: Failed to initialize DataStorage: {e}", exc_info=True
            )
            self.data_storage = None

        try:
            self.aircraft_db_manager = AircraftDatabaseManager()
            module_logger.info(
                "AircraftDatabaseManager initialized successfully by AppController."
            )
        except Exception as e:
            module_logger.critical(
                f"CRITICAL: Failed to initialize AircraftDatabaseManager: {e}",
                exc_info=True,
            )
            self.aircraft_db_manager = None
        module_logger.info("AppController initialized.")

    def set_main_window(self, main_window_instance: "MainWindow"):
        self.main_window = main_window_instance
        module_logger.debug(
            f"Main window instance ({type(main_window_instance)}) set in AppController."
        )
        initial_status_msg = "System Initialized. Ready."
        initial_status_level = GUI_STATUS_OK
        if not self.data_storage:
            err_msg_ds = "Data storage init failed. History will not be saved."
            module_logger.error(err_msg_ds)
            initial_status_msg = err_msg_ds
            initial_status_level = GUI_STATUS_ERROR
        if not self.aircraft_db_manager:
            err_msg_adb = "Aircraft DB init failed. Static details may be unavailable."
            module_logger.error(err_msg_adb)
            if initial_status_level == GUI_STATUS_OK:
                initial_status_msg = err_msg_adb
                initial_status_level = GUI_STATUS_WARNING
            else:
                initial_status_msg += f" {err_msg_adb}"
                initial_status_level = GUI_STATUS_ERROR

        if (
            self.main_window
            and hasattr(self.main_window, "root")
            and self.main_window.root.winfo_exists()
            and hasattr(self.main_window, "update_semaphore_and_status")
        ):
            self.main_window.update_semaphore_and_status(
                initial_status_level, initial_status_msg
            )
        else:
            module_logger.error(
                "Main window not set or lacks update_semaphore_and_status during set_main_window."
            )

    def _process_flight_data_queue(self):
        if not self.flight_data_queue:
            return
        if not (
            self.main_window
            and hasattr(self.main_window, "root")
            and self.main_window.root.winfo_exists()
        ):
            self._gui_after_id = None
            return

        flight_payloads_this_cycle: List[CanonicalFlightState] = []

        try:
            while not self.flight_data_queue.empty():
                message = None
                try:
                    message = self.flight_data_queue.get(block=False, timeout=0.01)
                except QueueEmpty:
                    break
                except Exception as e_q_get:
                    module_logger.warning(
                        f"Error getting from flight_data_queue: {e_q_get}"
                    )
                    continue

                if message is None:
                    continue

                try:
                    message_type = message.get("type")
                    if message_type == MSG_TYPE_FLIGHT_DATA:
                        flight_states_payload_chunk: Optional[
                            List[CanonicalFlightState]
                        ] = message.get("payload")
                        if flight_states_payload_chunk is not None:
                            flight_payloads_this_cycle.extend(
                                flight_states_payload_chunk
                            )

                            if self.data_storage:
                                saved_count = 0
                                for state in flight_states_payload_chunk:
                                    if not isinstance(state, CanonicalFlightState):
                                        module_logger.warning(
                                            f"Skipping non-CanonicalFlightState object in payload: {type(state)}"
                                        )
                                        continue
                                    try:
                                        flight_id = self.data_storage.add_or_update_flight_daily(
                                            icao24=state.icao24,
                                            callsign=state.callsign,
                                            origin_country=state.origin_country,
                                            detection_timestamp=state.timestamp,
                                        )
                                        if flight_id:
                                            pos_id = (
                                                self.data_storage.add_position_daily(
                                                    flight_id, state
                                                )
                                            )
                                            if pos_id:
                                                saved_count += 1
                                    except Exception as e_db_add:
                                        module_logger.error(
                                            f"Error saving flight/position to DB for ICAO {state.icao24}: {e_db_add}",
                                            exc_info=False,
                                        )
                                if saved_count > 0:
                                    module_logger.info(
                                        f"Saved {saved_count} position updates to DB from this chunk."
                                    )

                            if (
                                hasattr(self.main_window, "map_manager_instance")
                                and self.main_window.map_manager_instance is not None
                                and hasattr(
                                    self.main_window.map_manager_instance,
                                    "update_flights_on_map",
                                )
                                and self.is_live_monitoring_active
                                and self._active_bounding_box
                            ):
                                self.main_window.map_manager_instance.update_flights_on_map(
                                    flight_states_payload_chunk
                                )

                            gui_message = (
                                f"Live data: {len(flight_states_payload_chunk)} aircraft in chunk."
                                if flight_states_payload_chunk
                                else "Live data: No aircraft in area (chunk)."
                            )
                            if hasattr(self.main_window, "update_semaphore_and_status"):
                                try:
                                    self.main_window.update_semaphore_and_status(
                                        GUI_STATUS_OK, gui_message
                                    )
                                except tk.TclError:
                                    self._gui_after_id = None
                                    return
                        else:
                            if hasattr(self.main_window, "update_semaphore_and_status"):
                                try:
                                    self.main_window.update_semaphore_and_status(
                                        GUI_STATUS_WARNING,
                                        "Received empty data payload from adapter.",
                                    )
                                except tk.TclError:
                                    self._gui_after_id = None
                                    return

                    elif message_type == MSG_TYPE_ADAPTER_STATUS:
                        status_code = message.get("status_code")
                        gui_message_from_adapter = message.get(
                            "message", f"Adapter status: {status_code}"
                        )
                        gui_status_level_to_set = GUI_STATUS_UNKNOWN
                        action_required = None

                        if status_code == STATUS_PERMANENT_FAILURE:
                            action_required = "STOP_MONITORING"
                        elif status_code == STATUS_API_ERROR_TEMPORARY:
                            gui_status_level_to_set = GUI_STATUS_ERROR
                        elif status_code == STATUS_RATE_LIMITED:
                            gui_status_level_to_set = GUI_STATUS_WARNING
                        elif status_code == STATUS_FETCHING:
                            gui_status_level_to_set = GUI_STATUS_FETCHING
                        elif status_code in [
                            STATUS_STARTING,
                            STATUS_RECOVERED,
                            STATUS_STOPPED,
                        ]:
                            gui_status_level_to_set = GUI_STATUS_OK

                        if hasattr(self.main_window, "update_semaphore_and_status"):
                            try:
                                self.main_window.update_semaphore_and_status(
                                    gui_status_level_to_set, gui_message_from_adapter
                                )
                            except tk.TclError:
                                self._gui_after_id = None
                                return

                        if action_required == "STOP_MONITORING":
                            self.stop_live_monitoring(from_error=True)
                            break

                except Exception as e_msg_proc:
                    module_logger.error(
                        f"Error processing adapter message: {e_msg_proc}", exc_info=True
                    )
                finally:
                    try:
                        self.flight_data_queue.task_done()
                    except ValueError:
                        pass
                    except Exception as e_task_done:
                        module_logger.error(
                            f"Error calling task_done on flight_data_queue: {e_task_done}"
                        )

            # MODIFIED: Live update for detail window
            if (
                self.active_detail_window_ref
                and self.active_detail_window_icao
                and self.active_detail_window_ref.winfo_exists()
            ):

                flight_of_interest_updated_this_cycle = False
                latest_live_data_for_detail_icao: Optional[Dict[str, Any]] = None

                for state_obj in flight_payloads_this_cycle:
                    if state_obj.icao24 == self.active_detail_window_icao:
                        flight_of_interest_updated_this_cycle = True
                        latest_live_data_for_detail_icao = state_obj.to_dict()
                        # Consider this the "most live" data for the detail view from this batch
                        break

                if flight_of_interest_updated_this_cycle:
                    module_logger.info(
                        f"AppController: Flight {self.active_detail_window_icao} in detail view was in the latest data batch. Refreshing detail window."
                    )

                    static_data_upd: Optional[Dict[str, Any]] = None
                    if self.aircraft_db_manager:
                        static_data_upd = self.aircraft_db_manager.get_aircraft_details(
                            self.active_detail_window_icao
                        )

                    full_track_data_list_upd: List[Dict[str, Any]] = []
                    if self.data_storage:
                        try:
                            current_utc_date = datetime.now(
                                timezone.utc
                            )  # Or a date range if history is more complex
                            track_states_upd = (
                                self.data_storage.get_flight_track_for_icao_on_date(
                                    self.active_detail_window_icao, current_utc_date
                                )
                            )
                            if track_states_upd:
                                full_track_data_list_upd = [
                                    s.to_dict() for s in track_states_upd
                                ]
                        except Exception as e_track_upd:
                            module_logger.error(
                                f"Error retrieving updated track for detail view {self.active_detail_window_icao}: {e_track_upd}"
                            )

                    try:
                        self.active_detail_window_ref.update_details(
                            static_data_upd,
                            latest_live_data_for_detail_icao,
                            full_track_data_list_upd,
                        )
                    except tk.TclError:
                        module_logger.warning(
                            f"AppController: TclError trying to update detail window for {self.active_detail_window_icao}, likely closed."
                        )
                        self.details_window_closed(self.active_detail_window_icao)
                    except Exception as e_upd_detail_win:
                        module_logger.error(
                            f"AppController: Error updating detail window for {self.active_detail_window_icao}: {e_upd_detail_win}",
                            exc_info=True,
                        )

        except Exception as e_outer:
            module_logger.error(
                f"Outer error in _process_flight_data_queue: {e_outer}", exc_info=True
            )
        finally:
            if (
                self.is_live_monitoring_active
                and self.main_window
                and hasattr(self.main_window, "root")
                and self.main_window.root.winfo_exists()
            ):
                try:
                    self._gui_after_id = self.main_window.root.after(
                        GUI_QUEUE_CHECK_INTERVAL_MS, self._process_flight_data_queue
                    )
                except tk.TclError:
                    self._gui_after_id = None
                except Exception as e_after_schedule:
                    module_logger.error(
                        f"Error rescheduling _process_flight_data_queue: {e_after_schedule}"
                    )
                    self._gui_after_id = None
            else:
                self._gui_after_id = None

    def start_live_monitoring(self, bounding_box: Dict[str, float]):
        if not self.main_window:
            module_logger.error("Controller: Main window not set for live monitoring.")
            return
        if not bounding_box or not _is_valid_bbox_dict(bounding_box):
            err_msg = "Controller: Bounding box is required and must be valid."
            module_logger.error(err_msg)
            if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
                self.main_window._reset_gui_to_stopped_state(f"Start failed: {err_msg}")
            return

        if not self.data_storage and hasattr(
            self.main_window, "update_semaphore_and_status"
        ):
            self.main_window.update_semaphore_and_status(
                GUI_STATUS_WARNING, "DataStorage N/A. History will not be saved."
            )

        if self.is_live_monitoring_active:
            module_logger.warning(
                "Live monitoring requested but already active or stop in progress."
            )
            if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
                self.main_window._reset_gui_to_stopped_state(
                    "Monitoring stop in progress or already active."
                )
            return

        module_logger.info(
            f"Controller: Starting live monitoring for bbox: {bounding_box}"
        )
        self._active_bounding_box = bounding_box

        if (
            hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
            and hasattr(self.main_window.map_manager_instance, "set_target_bbox")
        ):
            try:
                self.main_window.map_manager_instance.set_target_bbox(bounding_box)
            except Exception as e_map:
                module_logger.error(f"Error setting map BBox: {e_map}", exc_info=True)
        else:
            if hasattr(self.main_window, "clear_all_views_data"):
                self.main_window.clear_all_views_data()

        if self.flight_data_queue is None:
            self.flight_data_queue = Queue(maxsize=200)
        else:
            while not self.flight_data_queue.empty():
                try:
                    self.flight_data_queue.get_nowait()
                    self.flight_data_queue.task_done()
                except QueueEmpty:
                    break
                except Exception:
                    break

        if self.live_adapter_thread and self.live_adapter_thread.is_alive():
            module_logger.info("Old adapter thread found alive. Stopping it first.")
            try:
                self.live_adapter_thread.stop()
                if self.main_window and self.main_window.root.winfo_exists():
                    self.main_window.root.update_idletasks()
                self.live_adapter_thread.join(timeout=ADAPTER_JOIN_TIMEOUT_SECONDS)
                if self.live_adapter_thread.is_alive():
                    module_logger.warning("Old adapter thread did not stop in time.")
            except Exception as e_join:
                module_logger.error(
                    f"Error stopping old adapter: {e_join}", exc_info=True
                )
            finally:
                self.live_adapter_thread = None

        self.live_adapter_thread = OpenSkyLiveAdapter(
            output_queue=self.flight_data_queue,
            bounding_box=self._active_bounding_box,
            polling_interval=app_config.LIVE_POLLING_INTERVAL_SECONDS,
        )
        self.is_live_monitoring_active = True
        self.live_adapter_thread.start()

        if (
            self._gui_after_id
            and self.main_window
            and self.main_window.root.winfo_exists()
        ):
            try:
                self.main_window.root.after_cancel(self._gui_after_id)
            except:
                pass
            finally:
                self._gui_after_id = None

        if self.main_window and self.main_window.root.winfo_exists():
            self._gui_after_id = self.main_window.root.after(
                100, self._process_flight_data_queue
            )
        else:
            module_logger.error(
                "Cannot schedule queue processor: MainWindow or root missing."
            )
            self.is_live_monitoring_active = False
            if self.live_adapter_thread and self.live_adapter_thread.is_alive():
                self.live_adapter_thread.stop()
            if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
                self.main_window._reset_gui_to_stopped_state("Start failed: GUI error.")

    def stop_live_monitoring(self, from_error: bool = False):
        if not self.is_live_monitoring_active and not (
            from_error
            and self.live_adapter_thread
            and self.live_adapter_thread.is_alive()
        ):
            if (
                hasattr(self.main_window, "_reset_gui_to_stopped_state")
                and not from_error
            ):
                self.main_window._reset_gui_to_stopped_state(
                    "Monitoring already stopped."
                )
            return

        module_logger.info(
            f"Controller: Stopping live monitoring (from_error={from_error})."
        )
        self.is_live_monitoring_active = False

        if (
            self._gui_after_id
            and self.main_window
            and hasattr(self.main_window, "root")
            and self.main_window.root.winfo_exists()
        ):
            try:
                self.main_window.root.after_cancel(self._gui_after_id)
            except tk.TclError:
                pass
            except Exception:
                pass
            finally:
                self._gui_after_id = None

        if self.live_adapter_thread and self.live_adapter_thread.is_alive():
            try:
                self.live_adapter_thread.stop()
                if self.main_window and self.main_window.root.winfo_exists():
                    self.main_window.root.update_idletasks()
                self.live_adapter_thread.join(timeout=ADAPTER_JOIN_TIMEOUT_SECONDS)
                if self.live_adapter_thread.is_alive():
                    module_logger.warning(
                        "Adapter thread did not stop in time on stop_live_monitoring."
                    )
            except Exception as e_join:
                module_logger.error(f"Error stopping adapter: {e_join}", exc_info=True)
            finally:
                self.live_adapter_thread = None

        if (
            self.flight_data_queue
            and self.main_window
            and hasattr(self.main_window, "root")
            and self.main_window.root.winfo_exists()
        ):
            try:
                # Process remaining queue items. Temporarily set active to True to allow one last run.
                original_active_state = self.is_live_monitoring_active
                self.is_live_monitoring_active = True  # Allow one last processing
                self._process_flight_data_queue()
                self.is_live_monitoring_active = original_active_state  # Restore state
            except Exception as e_final_q:
                module_logger.error(
                    f"Error in final queue processing: {e_final_q}", exc_info=True
                )

        if hasattr(self.main_window, "clear_all_views_data"):
            self.main_window.clear_all_views_data()

        if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
            msg = (
                "Monitoring stopped due to an error."
                if from_error
                else "Monitoring stopped."
            )
            try:
                self.main_window._reset_gui_to_stopped_state(msg)
            except Exception as e_reset:
                module_logger.error(f"Error resetting GUI: {e_reset}", exc_info=False)

        self._active_bounding_box = None

    def on_application_exit(self):
        module_logger.info(
            "Controller: Application exit requested. Cleaning up resources."
        )

        if (
            self.active_detail_window_ref
            and self.active_detail_window_ref.winfo_exists()
        ):
            try:
                module_logger.info(
                    f"Closing active detail window for {self.active_detail_window_icao} on app exit."
                )
                self.active_detail_window_ref.destroy()
            except Exception as e_close_detail:
                module_logger.error(
                    f"Error closing detail window on app exit: {e_close_detail}"
                )
            finally:  # Ensure these are cleared even if destroy fails for some reason
                self.active_detail_window_ref = None
                self.active_detail_window_icao = None

        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance is not None
        ):
            map_manager = self.main_window.map_manager_instance
            if hasattr(map_manager, "shutdown_worker") and callable(
                map_manager.shutdown_worker
            ):
                try:
                    map_manager.shutdown_worker()
                    module_logger.info(
                        "Controller: Main MapCanvasManager worker shutdown requested."
                    )
                except Exception as e_map_shutdown:
                    module_logger.error(
                        f"Controller: Error during Main MapCanvasManager worker shutdown: {e_map_shutdown}",
                        exc_info=True,
                    )

        is_adapter_considered_running = (
            self.live_adapter_thread and self.live_adapter_thread.is_alive()
        ) or self.is_live_monitoring_active
        if is_adapter_considered_running:
            self.stop_live_monitoring(from_error=False)

        if self.data_storage:
            try:
                self.data_storage.close_connection()
                module_logger.info("DataStorage connection closed.")
            except Exception as e_db_close:
                module_logger.error(
                    f"Error closing DataStorage: {e_db_close}", exc_info=True
                )
            finally:
                self.data_storage = None

        if self.aircraft_db_manager:
            try:
                self.aircraft_db_manager.close_connection()
                module_logger.info("AircraftDatabaseManager connection closed.")
            except Exception as e_ac_db_close:
                module_logger.error(
                    f"Error closing AircraftDatabaseManager: {e_ac_db_close}",
                    exc_info=True,
                )
            finally:
                self.aircraft_db_manager = None

        module_logger.info("Controller: Cleanup on application exit finished.")

    def start_history_monitoring(self):
        if not self.main_window:
            module_logger.error("Main window not set for history monitoring.")
            return
        if not self.data_storage:
            err_msg = "DataStorage not initialized. Cannot use history features."
            if hasattr(self.main_window, "update_semaphore_and_status"):
                self.main_window.update_semaphore_and_status(GUI_STATUS_ERROR, err_msg)
            if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
                self.main_window._reset_gui_to_stopped_state(
                    f"History start failed: {err_msg}"
                )
            return

        if hasattr(self.main_window, "update_semaphore_and_status"):
            self.main_window.update_semaphore_and_status(
                GUI_STATUS_OK, "History mode active (placeholder)."
            )
        module_logger.info("History monitoring started (placeholder).")

    def stop_history_monitoring(self):
        if not self.main_window:
            return
        if hasattr(self.main_window, "update_semaphore_and_status"):
            self.main_window.update_semaphore_and_status(
                GUI_STATUS_OK, "History monitoring stopped."
            )
        module_logger.info("History monitoring stopped (placeholder).")
        if hasattr(self.main_window, "_reset_gui_to_stopped_state"):
            self.main_window._reset_gui_to_stopped_state("History monitoring stopped.")

    def on_map_left_click(
        self, latitude: float, longitude: float, screen_x: int, screen_y: int
    ):
        module_logger.debug(
            f"Controller: Map left-clicked at Geo ({latitude:.5f}, {longitude:.5f})"
        )
        if self.main_window and hasattr(self.main_window, "update_clicked_map_info"):
            lat_dms_str, lon_dms_str = "N/A", "N/A"
            try:
                from ..map.map_utils import deg_to_dms_string

                lat_dms_str = (
                    deg_to_dms_string(latitude, "lat")
                    if latitude is not None
                    else "N/A"
                )
                lon_dms_str = (
                    deg_to_dms_string(longitude, "lon")
                    if longitude is not None
                    else "N/A"
                )
            except ImportError:
                module_logger.warning("map_utils.deg_to_dms_string N/A for left click.")
            except Exception as e_dms:
                module_logger.warning(
                    f"Error DMS for left click: {e_dms}", exc_info=False
                )
                lat_dms_str = lon_dms_str = "N/A (CalcErr)"

            try:
                self.main_window.update_clicked_map_info(
                    lat_deg=latitude,
                    lon_deg=longitude,
                    lat_dms=lat_dms_str,
                    lon_dms=lon_dms_str,
                )
            except tk.TclError as e_tcl:
                module_logger.warning(
                    f"TclError updating map clicked info panel: {e_tcl}. GUI closing."
                )
            except Exception as e_update:
                module_logger.error(
                    f"Error updating map clicked info panel: {e_update}", exc_info=False
                )

    def request_detailed_flight_info(self, icao24: str):
        normalized_icao24 = icao24.lower().strip()  # Normalize here as well for safety
        module_logger.info(
            f"Controller: Detailed info request for ICAO24: {normalized_icao24}"
        )
        if not self.main_window:
            module_logger.error(
                "Controller: MainWindow not set, cannot update flight details panel."
            )
            return

        if not normalized_icao24:
            if hasattr(self.main_window, "update_selected_flight_details"):
                self.main_window.update_selected_flight_details(None)
            return

        live_data_for_panel: Optional[Dict[str, Any]] = None
        static_data_for_panel: Optional[Dict[str, Any]] = None
        combined_details_for_panel: Dict[str, Any] = {"icao24": normalized_icao24}

        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
            and hasattr(
                self.main_window.map_manager_instance, "_current_flights_to_display_gui"
            )
            and hasattr(self.main_window.map_manager_instance, "_map_data_lock")
        ):
            map_mgr = self.main_window.map_manager_instance
            with map_mgr._map_data_lock:
                for state in map_mgr._current_flights_to_display_gui:
                    if state.icao24 == normalized_icao24:
                        live_data_for_panel = state.to_dict()
                        break
        if live_data_for_panel:
            combined_details_for_panel.update(live_data_for_panel)

        if self.aircraft_db_manager:
            static_data_for_panel = self.aircraft_db_manager.get_aircraft_details(
                normalized_icao24
            )
            if static_data_for_panel:
                for k, v in static_data_for_panel.items():
                    if k not in combined_details_for_panel:
                        combined_details_for_panel[k] = v

        if hasattr(self.main_window, "update_selected_flight_details"):
            self.main_window.update_selected_flight_details(combined_details_for_panel)

    def import_aircraft_database_from_file_with_progress(
        self, csv_filepath: str, progress_dialog_ref: "ImportProgressDialog"
    ):
        module_logger.info(
            f"Controller: Requesting aircraft DB import with progress from: {csv_filepath}"
        )
        if not self.aircraft_db_manager:
            module_logger.error(
                "AircraftDatabaseManager not initialized. Cannot import."
            )
            if progress_dialog_ref and progress_dialog_ref.winfo_exists():
                progress_dialog_ref.import_finished(
                    False, "Error: Database manager not active."
                )
            elif self.main_window and hasattr(self.main_window, "show_error_message"):
                self.main_window.show_error_message(
                    "Database Error", "Aircraft database manager not active."
                )
            return

        if not self.main_window or not self.main_window.root.winfo_exists():
            module_logger.error("MainWindow not available to start import thread.")
            if progress_dialog_ref and progress_dialog_ref.winfo_exists():
                progress_dialog_ref.import_finished(
                    False, "Error: Main application window not available."
                )
            return

        if progress_dialog_ref and progress_dialog_ref.winfo_exists():
            progress_dialog_ref.import_started()

        import_thread = threading.Thread(
            target=self._perform_db_import_with_progress_threaded,
            args=(csv_filepath, progress_dialog_ref),
            daemon=True,
        )
        import_thread.start()
        module_logger.info(f"Aircraft DB import thread started for: {csv_filepath}")

    def _count_csv_rows(self, csv_filepath: str) -> Optional[int]:
        try:
            current_limit = csv.field_size_limit()
            new_limit_target = 10 * 1024 * 1024
            if new_limit_target > current_limit:
                csv.field_size_limit(new_limit_target)
        except Exception:
            pass  # Ignore errors setting limit

        try:
            with open(csv_filepath, "r", encoding="utf-8-sig") as f:
                reader = csv.reader(f)
                try:
                    next(reader)
                except StopIteration:
                    return 0
                return sum(1 for _ in reader)
        except FileNotFoundError:
            return None
        except csv.Error:
            return None
        except Exception:
            return None

    def _perform_db_import_with_progress_threaded(
        self, csv_filepath: str, progress_dialog_ref: "ImportProgressDialog"
    ):
        if not self.aircraft_db_manager:
            module_logger.error("AircraftDBManager N/A in import thread.")
            if (
                progress_dialog_ref
                and progress_dialog_ref.winfo_exists()
                and self.main_window
                and self.main_window.root.winfo_exists()
            ):
                self.main_window.root.after(
                    0,
                    lambda: progress_dialog_ref.import_finished(
                        False, "Internal Error: DB Manager missing."
                    ),
                )
            return

        module_logger.info(f"Import thread: Starting row count for: {csv_filepath}")

        def schedule_gui_update(callable_func: Callable, *args: Any):
            if (
                self.main_window
                and hasattr(self.main_window, "root")
                and self.main_window.root.winfo_exists()
            ):
                try:
                    self.main_window.root.after(0, lambda: callable_func(*args))
                except Exception as e_after:
                    module_logger.error(
                        f"Error scheduling GUI update from import thread: {e_after}"
                    )

        total_data_rows = self._count_csv_rows(csv_filepath)
        if total_data_rows is None:
            if progress_dialog_ref and progress_dialog_ref.winfo_exists():
                schedule_gui_update(
                    progress_dialog_ref.import_finished,
                    False,
                    f"Error: Could not read/count rows in '{os.path.basename(csv_filepath)}'. Check logs.",
                )
            return
        if total_data_rows == 0:
            if progress_dialog_ref and progress_dialog_ref.winfo_exists():
                schedule_gui_update(
                    progress_dialog_ref.update_progress,
                    0,
                    0,
                    0,
                    f"File '{os.path.basename(csv_filepath)}' is empty or header-only.",
                )
                schedule_gui_update(
                    progress_dialog_ref.import_finished,
                    True,
                    "Import complete: No data rows to import.",
                )
            return

        if progress_dialog_ref and progress_dialog_ref.winfo_exists():
            schedule_gui_update(
                progress_dialog_ref.update_progress,
                0,
                0,
                total_data_rows,
                f"Found {total_data_rows} data rows. Starting import from '{os.path.basename(csv_filepath)}'...",
            )

        def import_progress_update_for_dialog_from_controller(
            processed_csv_rows, imported_db_rows, total_for_cb
        ):
            if progress_dialog_ref and progress_dialog_ref.winfo_exists():
                schedule_gui_update(
                    progress_dialog_ref.update_progress,
                    processed_csv_rows,
                    imported_db_rows,
                    total_for_cb,
                    f"Importing CSV row {processed_csv_rows}...",
                )

        processed_final, imported_final = self.aircraft_db_manager.import_from_csv(
            csv_filepath,
            replace_existing=True,
            progress_callback=import_progress_update_for_dialog_from_controller,
            total_rows_for_callback=total_data_rows,
        )

        final_message = f"DB Import complete. Processed: {processed_final}, Imported/Updated: {imported_final}."
        success = True
        if imported_final == 0 and processed_final > 0 and total_data_rows > 0:
            final_message = f"DB Import: {processed_final} CSV data rows processed, 0 imported (check CSV format/logs)."
        elif imported_final == 0 and processed_final == 0 and total_data_rows > 0:
            final_message = f"DB Import failed. Could not process rows from '{os.path.basename(csv_filepath)}'."
            success = False

        if progress_dialog_ref and progress_dialog_ref.winfo_exists():
            schedule_gui_update(
                progress_dialog_ref.update_progress,
                processed_final,
                imported_final,
                total_data_rows,
                "Finalizing import...",
            )
            schedule_gui_update(
                progress_dialog_ref.import_finished, success, final_message
            )

    def request_and_show_full_flight_details(self, icao24: str):
        normalized_icao24 = icao24.lower().strip()
        module_logger.info(
            f"Controller: Requesting to show full details for ICAO24: {normalized_icao24}"
        )

        if (
            not self.main_window
            or not hasattr(self.main_window, "root")
            or not self.main_window.root.winfo_exists()
        ):
            module_logger.error(
                "Controller: MainWindow not available to show full flight details."
            )
            return

        if not normalized_icao24:
            module_logger.warning("Controller: Empty ICAO24 for full details request.")
            if hasattr(self.main_window, "show_info_message"):
                self.main_window.show_info_message(
                    "Flight Details", "No ICAO24 provided for full details."
                )
            return

        # MODIFIED: Close existing detail window before opening a new one
        if (
            self.active_detail_window_ref
            and self.active_detail_window_ref.winfo_exists()
        ):
            # Do not destroy if it's for the same ICAO and already open (optional behavior)
            # For now, always destroy and recreate for simplicity of state.
            if self.active_detail_window_icao == normalized_icao24:
                module_logger.info(
                    f"Detail window for {normalized_icao24} already open. Re-focusing/Re-populating."
                )
                self.active_detail_window_ref.lift()
                self.active_detail_window_ref.focus_set()
                # No need to re-create, just update its content with potentially newer data
            else:
                module_logger.info(
                    f"Closing existing detail window for {self.active_detail_window_icao} before opening new one for {normalized_icao24}."
                )
                try:
                    self.active_detail_window_ref.destroy()
                except tk.TclError:
                    pass
                # details_window_closed will clear self.active_detail_window_ref & icao

        static_data: Optional[Dict[str, Any]] = None
        if self.aircraft_db_manager:
            static_data = self.aircraft_db_manager.get_aircraft_details(
                normalized_icao24
            )

        live_data: Optional[Dict[str, Any]] = None
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
            and hasattr(
                self.main_window.map_manager_instance, "_current_flights_to_display_gui"
            )
            and hasattr(self.main_window.map_manager_instance, "_map_data_lock")
        ):
            map_mgr = self.main_window.map_manager_instance
            with map_mgr._map_data_lock:
                for state in map_mgr._current_flights_to_display_gui:
                    if state.icao24 == normalized_icao24:
                        live_data = state.to_dict()
                        break

        full_track_data_list: List[Dict[str, Any]] = []
        if self.data_storage:
            try:
                current_utc_date = datetime.now(timezone.utc)
                track_states = self.data_storage.get_flight_track_for_icao_on_date(
                    normalized_icao24, current_utc_date
                )
                if track_states:
                    full_track_data_list = [state.to_dict() for state in track_states]
            except Exception as e_track:
                module_logger.error(
                    f"FullDetails: Error retrieving historical track for {normalized_icao24}: {e_track}",
                    exc_info=True,
                )

        try:
            from ..gui.dialogs.full_flight_details_window import FullFlightDetailsWindow

            # If we decided to re-focus/re-populate, and the window still exists
            if (
                self.active_detail_window_ref
                and self.active_detail_window_icao == normalized_icao24
                and self.active_detail_window_ref.winfo_exists()
            ):
                details_win = self.active_detail_window_ref
            else:  # Create new
                details_win = FullFlightDetailsWindow(
                    self.main_window.root, normalized_icao24, self
                )
                self.active_detail_window_ref = details_win
                self.active_detail_window_icao = normalized_icao24
                if self.main_window:
                    self.main_window.full_flight_details_window = details_win

            details_win.update_details(static_data, live_data, full_track_data_list)

        except ImportError:
            module_logger.error(
                "FullFlightDetailsWindow class not found. Cannot display full details."
            )
            if hasattr(self.main_window, "show_error_message"):
                self.main_window.show_error_message(
                    "UI Error", "Could not open full details window (import error)."
                )
        except Exception as e_show_details:
            module_logger.error(
                f"Error showing full flight details window for {normalized_icao24}: {e_show_details}",
                exc_info=True,
            )
            if hasattr(self.main_window, "show_error_message"):
                self.main_window.show_error_message(
                    "Error", f"Could not display full details: {e_show_details}"
                )
            self.active_detail_window_ref = None  # Clear refs if creation/update failed
            self.active_detail_window_icao = None
            if self.main_window:
                self.main_window.full_flight_details_window = None

    def details_window_closed(self, closed_icao24: str):
        normalized_closed_icao24 = closed_icao24.lower().strip()
        # Important: check if the closed ICAO matches the one we are actively tracking
        if self.active_detail_window_icao == normalized_closed_icao24:
            module_logger.info(
                f"AppController: Detail window for {normalized_closed_icao24} reported closed. Clearing references."
            )
            self.active_detail_window_ref = None
            self.active_detail_window_icao = None
            # Also clear MainWindow's convenience reference if it matches
            if (
                self.main_window
                and hasattr(self.main_window, "full_flight_details_window")
                and self.main_window.full_flight_details_window
                and not self.main_window.full_flight_details_window.winfo_exists()
            ):  # Check if already destroyed
                self.main_window.full_flight_details_window = None
        else:
            # This case can happen if a new detail window was opened before the old one finished its close notification
            module_logger.debug(
                f"AppController: A detail window for {normalized_closed_icao24} closed, but it was not the currently tracked active one ({self.active_detail_window_icao}). No action on active_detail references."
            )

    def on_map_right_click(
        self, latitude: float, longitude: float, screen_x: int, screen_y: int
    ):
        module_logger.debug(
            f"Controller: Map right-clicked at Geo ({latitude:.5f}, {longitude:.5f})"
        )
        if self.main_window:
            if hasattr(self.main_window, "update_clicked_map_info"):
                lat_dms_str, lon_dms_str = "N/A", "N/A"
                try:
                    from ..map.map_utils import deg_to_dms_string

                    lat_dms_str = (
                        deg_to_dms_string(latitude, "lat")
                        if latitude is not None
                        else "N/A"
                    )
                    lon_dms_str = (
                        deg_to_dms_string(longitude, "lon")
                        if longitude is not None
                        else "N/A"
                    )
                except ImportError:
                    module_logger.warning(
                        "map_utils.deg_to_dms_string N/A for right click."
                    )
                except Exception as e_dms:
                    module_logger.warning(
                        f"Error DMS for right click: {e_dms}", exc_info=False
                    )
                    lat_dms_str = lon_dms_str = "N/A (CalcErr)"
                try:
                    self.main_window.update_clicked_map_info(
                        lat_deg=latitude,
                        lon_deg=longitude,
                        lat_dms=lat_dms_str,
                        lon_dms=lon_dms_str,
                    )
                except tk.TclError as e_tcl:
                    module_logger.warning(
                        f"TclError updating map clicked info (right): {e_tcl}. GUI closing."
                    )
                except Exception as e_update:
                    module_logger.error(
                        f"Error updating map clicked info (right): {e_update}",
                        exc_info=False,
                    )

            if hasattr(self.main_window, "show_map_context_menu"):
                try:
                    self.main_window.show_map_context_menu(
                        latitude, longitude, screen_x, screen_y
                    )
                except tk.TclError as e_tcl_menu:
                    module_logger.warning(
                        f"TclError showing map context menu: {e_tcl_menu}. GUI closing."
                    )
                except Exception as e_menu:
                    module_logger.error(
                        f"Error showing map context menu: {e_menu}", exc_info=False
                    )
        else:
            module_logger.warning("Main window N/A for right click handling.")

    def on_map_context_menu_request(
        self, latitude: float, longitude: float, screen_x: int, screen_y: int
    ):
        module_logger.debug(
            f"Controller received context menu request for ({latitude:.5f}, {longitude:.5f}) at screen ({screen_x}, {screen_y})."
        )
        if self.main_window and hasattr(self.main_window, "show_map_context_menu"):
            try:
                self.main_window.show_map_context_menu(
                    latitude, longitude, screen_x, screen_y
                )
            except tk.TclError as e_tcl:
                module_logger.warning(
                    f"TclError delegating show_map_context_menu: {e_tcl}. GUI closing."
                )
            except Exception as e_menu:
                module_logger.error(
                    f"Error delegating show_map_context_menu: {e_menu}", exc_info=False
                )
        else:
            module_logger.warning("Main window N/A to show context menu.")

    def recenter_map_at_coords(self, lat: float, lon: float):
        module_logger.info(
            f"Controller request: recenter map at ({lat:.5f}, {lon:.5f})."
        )
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
        ):
            map_manager: "MapCanvasManager" = self.main_window.map_manager_instance
            if hasattr(map_manager, "recenter_map_at_coords"):
                try:
                    map_manager.recenter_map_at_coords(lat, lon)
                except Exception as e_recenter:
                    module_logger.error(
                        f"Error map_manager.recenter_map_at_coords: {e_recenter}",
                        exc_info=False,
                    )
            else:
                module_logger.warning(
                    "MapCanvasManager missing 'recenter_map_at_coords' method."
                )
        else:
            module_logger.warning(
                "Main window or MapCanvasManager N/A to recenter map."
            )

    def set_bbox_around_coords(
        self,
        center_lat: float,
        center_lon: float,
        area_size_km: float = DEFAULT_CLICK_AREA_SIZE_KM,
    ):
        module_logger.info(
            f"Controller request: set BBox ({area_size_km:.1f}km) around ({center_lat:.5f}, {center_lon:.5f})."
        )
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
        ):
            map_manager: "MapCanvasManager" = self.main_window.map_manager_instance
            if hasattr(map_manager, "set_bbox_around_coords"):
                try:
                    map_manager.set_bbox_around_coords(
                        center_lat, center_lon, area_size_km
                    )
                except Exception as e_set_bbox:
                    module_logger.error(
                        f"Error map_manager.set_bbox_around_coords: {e_set_bbox}",
                        exc_info=False,
                    )
            else:
                module_logger.warning(
                    "MapCanvasManager missing 'set_bbox_around_coords' method."
                )
        else:
            module_logger.warning("Main window or MapCanvasManager N/A to set BBox.")

    def update_bbox_gui_fields(self, bbox_dict: Dict[str, float]):
        module_logger.debug(
            f"Controller request: update BBox GUI fields with: {bbox_dict}"
        )
        if self.main_window and hasattr(self.main_window, "update_bbox_gui_fields"):
            try:
                self.main_window.update_bbox_gui_fields(bbox_dict)
            except tk.TclError as e_tcl:
                module_logger.warning(
                    f"TclError updating BBox GUI fields: {e_tcl}. GUI closing."
                )
            except Exception as e_update:
                module_logger.error(
                    f"Error updating BBox GUI fields: {e_update}", exc_info=False
                )
        else:
            module_logger.warning("Main window N/A to update BBox GUI fields.")

    def update_general_map_info(self):
        module_logger.debug("Controller: Request to update general map info.")
        if not (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
        ):
            if self.main_window and hasattr(
                self.main_window, "update_general_map_info_display"
            ):
                try:
                    self.main_window.update_general_map_info_display(
                        zoom=None,
                        map_size_str="N/A",
                        map_geo_bounds=None,
                        target_bbox_input=None,
                        flight_count=None,
                    )
                except Exception:
                    pass
            return

        map_manager: "MapCanvasManager" = self.main_window.map_manager_instance
        if hasattr(map_manager, "get_current_map_info"):
            map_info = {}
            try:
                map_info = map_manager.get_current_map_info()
                zoom = map_info.get("zoom")
                map_geo_bounds = map_info.get("map_geo_bounds")
                target_bbox_input_from_map_info = map_info.get("target_bbox_input")
                flight_count = map_info.get("flight_count")
                map_size_km_w = map_info.get("map_size_km_w")
                map_size_km_h = map_info.get("map_size_km_h")
                map_size_str = "N/A"
                if map_size_km_w is not None and map_size_km_h is not None:
                    try:
                        decimals = getattr(app_config, "MAP_SIZE_KM_DECIMAL_PLACES", 1)
                        map_size_str = f"{map_size_km_w:.{decimals}f}km x {map_size_km_h:.{decimals}f}km"
                    except Exception as e_format:
                        module_logger.warning(
                            f"Error formatting map size: {e_format}. Using N/A.",
                            exc_info=False,
                        )
                        map_size_str = "N/A (FormatErr)"

                if hasattr(self.main_window, "update_general_map_info_display"):
                    try:
                        self.main_window.update_general_map_info_display(
                            zoom=zoom,
                            map_size_str=map_size_str,
                            map_geo_bounds=map_geo_bounds,
                            target_bbox_input=target_bbox_input_from_map_info,
                            flight_count=flight_count,
                        )
                    except tk.TclError as e_tcl:
                        module_logger.warning(
                            f"TclError updating general map info panel: {e_tcl}. GUI closing."
                        )
                    except Exception as e_update:
                        module_logger.error(
                            f"Error updating general map info panel: {e_update}",
                            exc_info=False,
                        )
            except Exception as e_get_info:
                module_logger.error(
                    f"Error getting map info from map manager in AppController: {e_get_info}",
                    exc_info=True,
                )
                if hasattr(self.main_window, "update_general_map_info_display"):
                    try:
                        self.main_window.update_general_map_info_display(
                            zoom=None,
                            map_size_str="N/A",
                            map_geo_bounds=None,
                            target_bbox_input=None,
                            flight_count=None,
                        )
                    except Exception:
                        pass
        else:
            module_logger.warning(
                "MapCanvasManager missing 'get_current_map_info' method."
            )

    def map_zoom_in(self):
        module_logger.debug("Controller: Map Zoom In requested.")
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
        ):
            map_manager: "MapCanvasManager" = self.main_window.map_manager_instance
            if hasattr(map_manager, "zoom_in_at_center"):
                try:
                    map_manager.zoom_in_at_center()
                except Exception as e:
                    module_logger.error(
                        f"Error map_manager.zoom_in_at_center: {e}", exc_info=False
                    )
            else:
                module_logger.warning(
                    "MapCanvasManager missing 'zoom_in_at_center' method."
                )
        else:
            module_logger.warning("Map manager N/A for zoom in.")

    def map_zoom_out(self):
        module_logger.debug("Controller: Map Zoom Out requested.")
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
        ):
            map_manager: "MapCanvasManager" = self.main_window.map_manager_instance
            if hasattr(map_manager, "zoom_out_at_center"):
                try:
                    map_manager.zoom_out_at_center()
                except Exception as e:
                    module_logger.error(
                        f"Error map_manager.zoom_out_at_center: {e}", exc_info=False
                    )
            else:
                module_logger.warning(
                    "MapCanvasManager missing 'zoom_out_at_center' method."
                )
        else:
            module_logger.warning("Map manager N/A for zoom out.")

    def map_pan_direction(self, direction: str):
        module_logger.debug(f"Controller: Map Pan '{direction}' requested.")
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
        ):
            map_manager: "MapCanvasManager" = self.main_window.map_manager_instance
            if hasattr(map_manager, "pan_map_fixed_step"):
                try:
                    map_manager.pan_map_fixed_step(direction)
                except Exception as e:
                    module_logger.error(
                        f"Error map_manager.pan_map_fixed_step('{direction}'): {e}",
                        exc_info=False,
                    )
            else:
                module_logger.warning(
                    "MapCanvasManager missing 'pan_map_fixed_step' method."
                )
        else:
            module_logger.warning(f"Map manager N/A for pan {direction}.")

    def map_center_on_coords_and_fit_patch(
        self, lat: float, lon: float, patch_size_km: float
    ):
        module_logger.debug(
            f"Controller: Center map at ({lat:.4f},{lon:.4f}) and fit {patch_size_km}km patch."
        )
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance
        ):
            map_manager: "MapCanvasManager" = self.main_window.map_manager_instance
            if hasattr(map_manager, "center_map_and_fit_patch"):
                try:
                    map_manager.center_map_and_fit_patch(lat, lon, patch_size_km)
                except Exception as e:
                    module_logger.error(
                        f"Error map_manager.center_map_and_fit_patch: {e}",
                        exc_info=False,
                    )
            else:
                module_logger.warning(
                    "MapCanvasManager missing 'center_map_and_fit_patch' method."
                )
        else:
            module_logger.warning("Map manager N/A for center and fit patch.")

    def set_map_track_length(self, length: int):
        module_logger.info(
            f"Controller: Request to set map track length to {length} points."
        )
        if (
            self.main_window
            and hasattr(self.main_window, "map_manager_instance")
            and self.main_window.map_manager_instance is not None
            and hasattr(self.main_window.map_manager_instance, "set_max_track_points")
        ):
            try:
                self.main_window.map_manager_instance.set_max_track_points(length)
            except Exception as e:
                module_logger.error(
                    f"Error calling map_manager.set_max_track_points({length}): {e}",
                    exc_info=True,
                )
                if hasattr(self.main_window, "show_error_message"):
                    self.main_window.show_error_message(
                        "Map Configuration Error",
                        f"Failed to set track length on map: {e}",
                    )
        else:
            module_logger.warning(
                "MapCanvasManager or 'set_max_track_points' N/A to set track length."
            )
