# FlightMonitor/gui/panels/map_info_panel.py
"""
Panel for displaying various map-related information:
clicked coordinates, map bounds, target bounding box, zoom level,
flight count, and map size.
"""
import tkinter as tk
from tkinter import ttk
from typing import Dict, Any, Optional, Tuple

from flightmonitor.utils.logger import get_logger
from flightmonitor.data import config as app_config
from flightmonitor.map.map_utils import _is_valid_bbox_dict # Used for BBox validation internally
from flightmonitor.utils.gui_utils import GUI_STATUS_UNKNOWN # Used for default color in status updates


module_logger = get_logger(__name__)

# Colors for BBox status display, matched with MainWindow's previous definitions
BBOX_COLOR_INSIDE = "green4"
BBOX_COLOR_OUTSIDE = "red2"
BBOX_COLOR_PARTIAL = "darkorange"
BBOX_COLOR_NA = "gray50"


class MapInfoPanel:
    """
    Manages the GUI elements for displaying map-related information.
    """

    def __init__(self, parent_frame: ttk.Frame):
        """
        Initializes the MapInfoPanel.

        Args:
            parent_frame: The parent ttk.Frame where this panel will be placed.
        """
        self.parent_frame = parent_frame
        # Configura le colonne per un layout a griglia pulito, con le etichette dei valori più piccole.
        self.parent_frame.columnconfigure(1, weight=0)
        self.parent_frame.columnconfigure(3, weight=0)

        # --- Clicked Map Coordinates ---
        info_row = 0
        ttk.Label(self.parent_frame, text="Click Lat:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_lat_value = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_lat_value.grid(
            row=info_row, column=1, sticky=tk.W, pady=1, padx=(0, 5)
        )
        ttk.Label(self.parent_frame, text="Lon:").grid(
            row=info_row, column=2, sticky=tk.W, pady=1, padx=(5, 2)
        )
        self.info_lon_value = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_lon_value.grid(
            row=info_row, column=3, sticky=tk.W, pady=1, padx=(0, 5)
        )
        info_row += 1
        ttk.Label(self.parent_frame, text="Lat DMS:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_lat_dms_value = ttk.Label(
            self.parent_frame, text="N/A", width=18, wraplength=140
        )
        self.info_lat_dms_value.grid(
            row=info_row, column=1, sticky=tk.W, pady=1, padx=(0, 5)
        )
        ttk.Label(self.parent_frame, text="Lon DMS:").grid(
            row=info_row, column=2, padx=(5, 2), pady=2, sticky=tk.W
        )
        self.info_lon_dms_value = ttk.Label(
            self.parent_frame, text="N/A", width=18, wraplength=140
        )
        self.info_lon_dms_value.grid(
            row=info_row, column=3, padx=(0, 0), pady=2, sticky=tk.EW
        )
        info_row += 1
        ttk.Separator(self.parent_frame, orient=tk.HORIZONTAL).grid(
            row=info_row, column=0, columnspan=4, sticky=tk.EW, pady=2
        )

        # --- Current Map Geographic Bounds ---
        info_row += 1
        ttk.Label(self.parent_frame, text="Map N:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_map_bounds_n = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_map_bounds_n.grid(
            row=info_row, column=1, sticky=tk.W, pady=1, padx=(0, 5)
        )
        ttk.Label(self.parent_frame, text="W:").grid(
            row=info_row, column=2, sticky=tk.W, pady=1, padx=(5, 2)
        )
        self.info_map_bounds_w = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_map_bounds_w.grid(
            row=info_row, column=3, sticky=tk.W, pady=1, padx=(0, 5)
        )
        info_row += 1
        ttk.Label(self.parent_frame, text="Map S:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_map_bounds_s = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_map_bounds_s.grid(
            row=info_row, column=1, sticky=tk.W, pady=1, padx=(0, 5)
        )
        ttk.Label(self.parent_frame, text="E:").grid(
            row=info_row, column=2, sticky=tk.W, pady=1, padx=(5, 2)
        )
        self.info_map_bounds_e = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_map_bounds_e.grid(
            row=info_row, column=3, sticky=tk.W, pady=1, padx=(0, 5)
        )
        info_row += 1
        ttk.Separator(self.parent_frame, orient=tk.HORIZONTAL).grid(
            row=info_row, column=0, columnspan=4, sticky=tk.EW, pady=2
        )

        # --- Target Bounding Box Input (Monitoring Area) ---
        info_row += 1
        ttk.Label(self.parent_frame, text="Target N:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_target_bbox_n = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_target_bbox_n.grid(
            row=info_row, column=1, sticky=tk.W, pady=1, padx=(0, 5)
        )
        ttk.Label(self.parent_frame, text="W:").grid(
            row=info_row, column=2, padx=(5, 2), pady=2, sticky=tk.W
        )
        self.info_target_bbox_w = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_target_bbox_w.grid(
            row=info_row, column=3, padx=(0, 5), pady=1, sticky=tk.W
        )
        info_row += 1
        ttk.Label(self.parent_frame, text="Target S:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_target_bbox_s = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_target_bbox_s.grid(
            row=info_row, column=1, sticky=tk.W, pady=1, padx=(0, 5)
        )
        ttk.Label(self.parent_frame, text="E:").grid(
            row=info_row, column=2, padx=(5, 2), pady=2, sticky=tk.W
        )
        self.info_target_bbox_e = ttk.Label(self.parent_frame, text="N/A", width=12)
        self.info_target_bbox_e.grid(
            row=info_row, column=3, padx=(0, 5), pady=1, sticky=tk.W
        )
        info_row += 1
        ttk.Separator(self.parent_frame, orient=tk.HORIZONTAL).grid(
            row=info_row, column=0, columnspan=4, sticky=tk.EW, pady=2
        )

        # --- General Map Metrics (Zoom, Flights, Size) ---
        info_row += 1
        ttk.Label(self.parent_frame, text="Zoom:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_zoom_value = ttk.Label(self.parent_frame, text="N/A", width=4)
        self.info_zoom_value.grid(
            row=info_row, column=1, sticky=tk.W, pady=1, padx=(0, 5)
        )
        ttk.Label(self.parent_frame, text="Flights:").grid(
            row=info_row, column=2, padx=(5, 2), pady=2, sticky=tk.W
        )
        self.info_flight_count_value = ttk.Label(self.parent_frame, text="N/A", width=5)
        self.info_flight_count_value.grid(
            row=info_row, column=3, padx=(0, 5), pady=1, sticky=tk.W
        )
        info_row += 1
        ttk.Label(self.parent_frame, text="Map Size:").grid(
            row=info_row, column=0, sticky=tk.W, pady=1, padx=(0, 2)
        )
        self.info_map_size_value = ttk.Label(self.parent_frame, text="N/A", wraplength=180)
        self.info_map_size_value.grid(
            row=info_row, column=1, columnspan=3, sticky=tk.W, pady=1, padx=(0, 5)
        )

        module_logger.debug("MapInfoPanel initialized.")

    def update_clicked_map_info(
        self,
        lat_deg: Optional[float],
        lon_deg: Optional[float],
        lat_dms: str,
        lon_dms: str,
    ):
        """
        Updates the display for the geographic coordinates of a clicked point on the map.

        Args:
            lat_deg: Latitude in decimal degrees (Optional).
            lon_deg: Longitude in decimal degrees (Optional).
            lat_dms: Latitude formatted as Degrees-Minutes-Seconds string.
            lon_dms: Longitude formatted as Degrees-Minutes-Seconds string.
        """
        if not (hasattr(self, "info_lat_value") and self.info_lat_value.winfo_exists()):
            return # Panel not fully initialized or destroyed

        decimals = getattr(app_config, "COORDINATE_DECIMAL_PLACES", 5)
        try:
            # Update decimal degree labels
            self.info_lat_value.config(
                text=f"{lat_deg:.{decimals}f}" if lat_deg is not None else "N/A"
            )
            self.info_lon_value.config(
                text=f"{lon_deg:.{decimals}f}" if lon_deg is not None else "N/A"
            )
            # Update DMS labels (already formatted strings)
            self.info_lat_dms_value.config(text=lat_dms or "N/A")
            self.info_lon_dms_value.config(text=lon_dms or "N/A")
        except tk.TclError:
            # Widgets might be destroyed during application shutdown
            module_logger.debug("TclError updating clicked map info, widgets likely destroyed.")
        except Exception as e_update_click:
            module_logger.error(f"Error updating clicked map info panel: {e_update_click}", exc_info=False)


    def update_general_map_info_display(
        self,
        zoom: Optional[int],
        map_size_str: str,  # Already formatted (e.g., "100km x 80km")
        map_geo_bounds: Optional[Tuple[float, float, float, float]],  # (W, S, E, N)
        target_bbox_input: Optional[Dict[str, float]],  # Standard dict {lat_min, lon_min, lat_max, lon_max}
        flight_count: Optional[int],
    ):
        """
        Updates the display for general map information like current map bounds,
        target monitoring area, zoom level, total flights shown, and map size.

        Args:
            zoom: Current map zoom level.
            map_size_str: Formatted string representing the map's current geographic size (e.g., "500km x 300km").
            map_geo_bounds: Tuple (west_lon, south_lat, east_lon, north_lat) representing the currently displayed map's bounds.
            target_bbox_input: Dictionary with keys "lat_min", "lon_min", "lat_max", "lon_max"
                               representing the user-defined monitoring area.
            flight_count: Number of flights currently displayed on the map.
        """
        if not (hasattr(self, "info_zoom_value") and self.info_zoom_value.winfo_exists()):
            return # Panel not fully initialized or destroyed

        decimals = getattr(app_config, "COORDINATE_DECIMAL_PLACES", 5)
        try:
            # --- Map Geographic Bounds ---
            map_w, map_s, map_e, map_n = ("N/A",) * 4
            if map_geo_bounds:
                map_w_val, map_s_val, map_e_val, map_n_val = map_geo_bounds
                map_w, map_s, map_e, map_n = (
                    f"{map_w_val:.{decimals}f}",
                    f"{map_s_val:.{decimals}f}",
                    f"{map_e_val:.{decimals}f}",
                    f"{map_n_val:.{decimals}f}",
                )

            self.info_map_bounds_w.config(text=map_w)
            self.info_map_bounds_s.config(text=map_s)
            self.info_map_bounds_e.config(text=map_e)
            self.info_map_bounds_n.config(text=map_n)

            # --- Target BBox Input (Monitoring Area) ---
            target_w, target_s, target_e, target_n = ("N/A",) * 4
            color_for_target_bbox = BBOX_COLOR_NA  # Default color

            if target_bbox_input and _is_valid_bbox_dict(target_bbox_input):
                # Ensure correct mapping from dictionary keys to geographic coordinates for display
                target_n = f"{target_bbox_input['lat_max']:.{decimals}f}" # North: max_lat
                target_w = f"{target_bbox_input['lon_min']:.{decimals}f}" # West: min_lon
                target_s = f"{target_bbox_input['lat_min']:.{decimals}f}" # South: min_lat
                target_e = f"{target_bbox_input['lon_max']:.{decimals}f}" # East: max_lon
                
                # Determine color based on how target BBox relates to current map view
                if map_geo_bounds:
                    relation_status = self._is_bbox_inside_bbox(
                        target_bbox_input, map_geo_bounds
                    )
                    if relation_status == "Inside":
                        color_for_target_bbox = BBOX_COLOR_INSIDE
                    elif relation_status == "Partial":
                        color_for_target_bbox = BBOX_COLOR_PARTIAL
                    else:
                        color_for_target_bbox = BBOX_COLOR_OUTSIDE
                else:  # No map view bounds, can't determine relation
                    color_for_target_bbox = BBOX_COLOR_NA

            self.info_target_bbox_n.config(text=target_n, foreground=color_for_target_bbox)
            self.info_target_bbox_w.config(text=target_w, foreground=color_for_target_bbox)
            self.info_target_bbox_s.config(text=target_s, foreground=color_for_target_bbox)
            self.info_target_bbox_e.config(text=target_e, foreground=color_for_target_bbox)

            # --- Other General Info ---
            self.info_zoom_value.config(text=str(zoom) if zoom is not None else "N/A")
            self.info_map_size_value.config(text=map_size_str or "N/A")
            self.info_flight_count_value.config(text=str(flight_count) if flight_count is not None else "N/A")

        except tk.TclError:
            module_logger.debug("TclError updating general map info, widgets likely destroyed.")
        except Exception as e_update_gen_info:
            module_logger.error(f"Error updating general map info panel: {e_update_gen_info}", exc_info=False)

    def _is_bbox_inside_bbox(
        self,
        inner_bbox_dict: Dict[str, float],  # Standard dict {lat_min, lon_min, ...}
        outer_bbox_tuple: Tuple[float, float, float, float],  # (W, S, E, N)
    ) -> str:
        """
        Determines if an inner bounding box is fully inside, partially overlaps,
        or is entirely outside of an outer bounding box.

        Args:
            inner_bbox_dict: The inner bounding box as a dictionary.
            outer_bbox_tuple: The outer bounding box as a tuple (west, south, east, north).

        Returns:
            "Inside", "Outside", "Partial", or "N/A" if inputs are invalid.
        """
        # Validate inputs using _is_valid_bbox_dict and basic tuple checks
        if not _is_valid_bbox_dict(inner_bbox_dict) or not (
            outer_bbox_tuple
            and len(outer_bbox_tuple) == 4
            and all(isinstance(c, (int, float)) for c in outer_bbox_tuple)
        ):
            return "N/A"

        # Convert outer tuple to a comparable dict for clarity and consistency
        outer_dict = {
            "lon_min": outer_bbox_tuple[0],
            "lat_min": outer_bbox_tuple[1],
            "lon_max": outer_bbox_tuple[2],
            "lat_max": outer_bbox_tuple[3],
        }
        eps = 1e-6  # Epsilon for robust float comparisons

        # Check for full containment
        # If inner's min is >= outer's min, AND inner's max is <= outer's max for both lat/lon
        if (
            inner_bbox_dict["lon_min"] >= outer_dict["lon_min"] - eps
            and inner_bbox_dict["lat_min"] >= outer_dict["lat_min"] - eps
            and inner_bbox_dict["lon_max"] <= outer_dict["lon_max"] + eps
            and inner_bbox_dict["lat_max"] <= outer_dict["lat_max"] + eps
        ):
            return "Inside"

        # Check for no overlap (completely outside)
        # If inner is entirely to the right OR entirely to the left OR entirely above OR entirely below the outer
        if (
            inner_bbox_dict["lon_min"] >= outer_dict["lon_max"] - eps  # Inner right of outer
            or inner_bbox_dict["lon_max"] <= outer_dict["lon_min"] + eps  # Inner left of outer
            or inner_bbox_dict["lat_min"] >= outer_dict["lat_max"] - eps  # Inner above outer
            or inner_bbox_dict["lat_max"] <= outer_dict["lat_min"] + eps # Inner below outer
        ):
            return "Outside"

        # If it's not fully inside and not entirely outside, it must be partial
        return "Partial"