# FlightMonitor/data/storage.py
import sqlite3
import os
from datetime import (
    datetime,
    timezone,
    timedelta,
)
from typing import Optional, List, Dict, Any
import time

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

module_logger = get_logger(__name__)


class DataStorage:
    """
    Handles storage of flight data into daily SQLite database files.
    """

    def __init__(self):
        self._current_db_path: Optional[str] = None
        self._connection: Optional[sqlite3.Connection] = None
        self._ensure_database_directory()
        module_logger.info("DataStorage initialized.")

    def _ensure_database_directory(self):
        try:
            abs_db_dir = os.path.abspath(app_config.DATABASE_DIRECTORY)
            if not os.path.exists(abs_db_dir):
                os.makedirs(abs_db_dir)
                module_logger.info(f"Database directory created: {abs_db_dir}")
            if not os.path.isdir(abs_db_dir):
                raise OSError(f"Path {abs_db_dir} exists but is not a directory.")
            if not os.access(abs_db_dir, os.W_OK):
                raise OSError(f"Directory {abs_db_dir} is not writable.")
        except OSError as e:
            module_logger.error(
                f"Database directory error for {app_config.DATABASE_DIRECTORY}: {e}",
                exc_info=True,
            )
            raise

    def _get_db_path_for_date(self, target_date_utc: Optional[datetime] = None) -> str:
        if target_date_utc is None:
            target_date_utc = datetime.now(timezone.utc)
        db_filename = target_date_utc.strftime(app_config.DATABASE_FILENAME_FORMAT)
        db_path = os.path.join(
            os.path.abspath(app_config.DATABASE_DIRECTORY), db_filename
        )
        return db_path

    def _get_db_connection(
        self, for_date_utc: Optional[datetime] = None
    ) -> Optional[sqlite3.Connection]:
        target_db_path = self._get_db_path_for_date(for_date_utc)
        if self._connection and self._current_db_path == target_db_path:
            return self._connection
        if self._connection:
            try:
                self._connection.close()
                module_logger.info(f"Closed DB connection to: {self._current_db_path}")
            except sqlite3.Error as e:
                module_logger.error(
                    f"Error closing previous DB connection to {self._current_db_path}: {e}"
                )
        self._connection = None
        self._current_db_path = target_db_path
        try:
            self._connection = sqlite3.connect(target_db_path, timeout=10.0)
            self._connection.execute("PRAGMA foreign_keys = ON;")
            self._connection.row_factory = sqlite3.Row
            module_logger.info(f"Opened DB connection to: {target_db_path}")
            self._init_tables_if_not_exist(self._connection)
            return self._connection
        except sqlite3.Error as e:
            module_logger.error(
                f"Failed to connect/initialize database {target_db_path}: {e}",
                exc_info=True,
            )
            if self._connection:
                try:
                    self._connection.close()
                except sqlite3.Error:
                    pass
            self._connection = None
            self._current_db_path = None
            return None

    def _init_tables_if_not_exist(self, db_conn: sqlite3.Connection):
        cursor = None
        try:
            cursor = db_conn.cursor()
            cursor.execute(
                """
                CREATE TABLE IF NOT EXISTS flights (
                    flight_id INTEGER PRIMARY KEY AUTOINCREMENT,
                    icao24 TEXT NOT NULL UNIQUE,
                    callsign TEXT,
                    origin_country TEXT,
                    first_seen_day REAL NOT NULL,
                    last_seen_day REAL NOT NULL
                )
            """
            )
            
            # MODIFICA: Aggiunto vincolo UNIQUE alla tabella positions
            cursor.execute(
                """
                CREATE TABLE IF NOT EXISTS positions (
                    position_id INTEGER PRIMARY KEY AUTOINCREMENT,
                    flight_id INTEGER NOT NULL,
                    detection_timestamp REAL NOT NULL,
                    latitude REAL NOT NULL,
                    longitude REAL NOT NULL,
                    baro_altitude_m REAL,
                    geo_altitude_m REAL,
                    velocity_mps REAL,
                    true_track_deg REAL,
                    vertical_rate_mps REAL,
                    on_ground BOOLEAN NOT NULL,
                    squawk TEXT,
                    spi BOOLEAN,
                    position_source TEXT,
                    raw_data_provider TEXT,
                    recorded_at REAL NOT NULL,
                    FOREIGN KEY (flight_id) REFERENCES flights(flight_id) ON DELETE CASCADE,
                    UNIQUE(flight_id, detection_timestamp)
                )
            """
            )
            cursor.execute(
                "CREATE INDEX IF NOT EXISTS idx_positions_flight_id_timestamp ON positions (flight_id, detection_timestamp);"
            )
            cursor.execute(
                "CREATE INDEX IF NOT EXISTS idx_flights_icao24 ON flights (icao24);"
            )
            cursor.execute(
                "CREATE INDEX IF NOT EXISTS idx_positions_detection_timestamp ON positions (detection_timestamp);"
            )
            db_conn.commit()
            module_logger.debug(
                f"Database tables and indexes ensured in {self._current_db_path}."
            )
        except sqlite3.Error as e:
            module_logger.error(
                f"Error initializing tables in {self._current_db_path}: {e}",
                exc_info=True,
            )
            if db_conn:
                db_conn.rollback()
        finally:
            if cursor:
                cursor.close()

    def add_or_update_flight_daily(
        self,
        icao24: str,
        callsign: Optional[str],
        origin_country: Optional[str],
        detection_timestamp: float,
    ) -> Optional[int]:
        if not icao24:
            module_logger.warning("icao24 is required to add or update a flight.")
            return None

        event_date_utc = datetime.fromtimestamp(detection_timestamp, timezone.utc)
        conn = self._get_db_connection(for_date_utc=event_date_utc)
        if not conn:
            module_logger.error(
                "Failed to get DB connection for add_or_update_flight_daily."
            )
            return None

        cursor = None
        flight_id: Optional[int] = None
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT flight_id, callsign FROM flights WHERE icao24 = ?", (icao24,)
            )
            row = cursor.fetchone()

            if row:
                flight_id = row["flight_id"]
                existing_callsign = row["callsign"]
                final_callsign = existing_callsign
                if callsign and callsign != existing_callsign:
                    final_callsign = callsign
                elif not existing_callsign and callsign:
                    final_callsign = callsign

                cursor.execute(
                    """
                    UPDATE flights SET last_seen_day = ?, callsign = ?
                    WHERE flight_id = ?
                """,
                    (detection_timestamp, final_callsign, flight_id),
                )
                module_logger.debug(
                    f"Updated flight icao24={icao24}, flight_id={flight_id}, last_seen_day={detection_timestamp:.0f} in {self._current_db_path}"
                )
            else:
                cursor.execute(
                    """
                    INSERT INTO flights (icao24, callsign, origin_country, first_seen_day, last_seen_day)
                    VALUES (?, ?, ?, ?, ?)
                """,
                    (
                        icao24,
                        callsign,
                        origin_country,
                        detection_timestamp,
                        detection_timestamp,
                    ),
                )
                flight_id = cursor.lastrowid
                module_logger.info(
                    f"Added new flight icao24={icao24}, flight_id={flight_id} to {self._current_db_path}"
                )
            conn.commit()
        except sqlite3.Error as e:
            module_logger.error(
                f"DB error in add_or_update_flight_daily for icao24={icao24}: {e}",
                exc_info=True,
            )
            if conn:
                conn.rollback()
            flight_id = None
        finally:
            if cursor:
                cursor.close()
        return flight_id

    def add_position_daily(
        self, flight_id: int, flight_state_obj: CanonicalFlightState
    ) -> Optional[int]:
        if not flight_id:
            module_logger.warning("flight_id is required to add a position.")
            return None
        if not isinstance(flight_state_obj, CanonicalFlightState):
            module_logger.error(
                f"Invalid type for flight_state_obj: expected CanonicalFlightState, got {type(flight_state_obj)}"
            )
            return None

        event_date_utc = datetime.fromtimestamp(
            flight_state_obj.timestamp, timezone.utc
        )
        conn = self._get_db_connection(for_date_utc=event_date_utc)
        if not conn:
            module_logger.error(
                f"Failed to get DB connection for add_position_daily (flight_id={flight_id})."
            )
            return None

        cursor = None
        position_id: Optional[int] = None
        recorded_at_ts = time.time()

        if flight_state_obj.latitude is None or flight_state_obj.longitude is None:
            module_logger.warning(
                f"Skipping position for flight_id={flight_id} (ICAO: {flight_state_obj.icao24}) "
                f"due to missing latitude/longitude. Timestamp: {flight_state_obj.timestamp:.0f}"
            )
            return None

        try:
            cursor = conn.cursor()
            
            # MODIFICA: Utilizza INSERT OR IGNORE per evitare duplicati
            sql = """
                INSERT OR IGNORE INTO positions (
                    flight_id, detection_timestamp, latitude, longitude,
                    baro_altitude_m, geo_altitude_m, velocity_mps, true_track_deg,
                    vertical_rate_mps, on_ground, squawk, spi, position_source,
                    raw_data_provider, recorded_at
                ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            """
            params = (
                flight_id,
                flight_state_obj.timestamp,
                flight_state_obj.latitude,
                flight_state_obj.longitude,
                flight_state_obj.baro_altitude_m,
                flight_state_obj.geo_altitude_m,
                flight_state_obj.velocity_mps,
                flight_state_obj.true_track_deg,
                flight_state_obj.vertical_rate_mps,
                flight_state_obj.on_ground,
                flight_state_obj.squawk,
                flight_state_obj.spi,
                (
                    str(flight_state_obj.position_source)
                    if flight_state_obj.position_source is not None
                    else None
                ),
                flight_state_obj.raw_data_provider,
                recorded_at_ts,
            )

            cursor.execute(sql, params)
            
            # Se la riga è stata inserita (non ignorata), changes sarà 1
            if cursor.rowcount > 0:
                 position_id = cursor.lastrowid
                 module_logger.debug(f"Added position for flight_id={flight_id} at {flight_state_obj.timestamp:.0f} (pos_id={position_id})")
            else:
                 module_logger.debug(f"Ignored duplicate position for flight_id={flight_id} at {flight_state_obj.timestamp:.0f}")

            conn.commit()
        except sqlite3.Error as e:
            module_logger.error(
                f"DB error in add_position_daily for flight_id={flight_id} (ICAO: {flight_state_obj.icao24}): {e}",
                exc_info=True,
            )
            if conn:
                conn.rollback()
            position_id = None
        finally:
            if cursor:
                cursor.close()
        return position_id

    def get_flight_track_for_icao_on_date(
        self, 
        icao24_to_find: str, 
        target_date_utc: Optional[datetime] = None,
        since_timestamp: Optional[float] = None
    ) -> List[CanonicalFlightState]:
        if not icao24_to_find:
            return []

        if target_date_utc is None:
            target_date_utc = datetime.now(timezone.utc)

        conn = self._get_db_connection(for_date_utc=target_date_utc)
        if not conn:
            return []

        track_points: List[CanonicalFlightState] = []
        cursor = None
        try:
            cursor = conn.cursor()
            cursor.execute(
                "SELECT flight_id FROM flights WHERE icao24 = ?",
                (icao24_to_find.lower(),),
            )
            flight_row = cursor.fetchone()

            if not flight_row:
                return []

            flight_id = flight_row["flight_id"]
            
            sql_query = "SELECT * FROM positions WHERE flight_id = ?"
            params = [flight_id]

            if since_timestamp is not None:
                sql_query += " AND detection_timestamp >= ?"
                params.append(since_timestamp)

            sql_query += " ORDER BY detection_timestamp ASC"

            cursor.execute(sql_query, params)
            position_rows = cursor.fetchall()

            if not position_rows:
                return []

            for pos_row in position_rows:
                try:
                    state = CanonicalFlightState(
                        icao24=icao24_to_find,
                        timestamp=pos_row["detection_timestamp"],
                        last_contact_timestamp=pos_row["detection_timestamp"],
                        latitude=pos_row["latitude"],
                        longitude=pos_row["longitude"],
                        on_ground=bool(pos_row["on_ground"]),
                        callsign=None,
                        origin_country=None,
                        baro_altitude_m=pos_row["baro_altitude_m"],
                        geo_altitude_m=pos_row["geo_altitude_m"],
                        velocity_mps=pos_row["velocity_mps"],
                        true_track_deg=pos_row["true_track_deg"],
                        vertical_rate_mps=pos_row["vertical_rate_mps"],
                        squawk=pos_row["squawk"],
                        spi=bool(pos_row["spi"]) if pos_row["spi"] is not None else None,
                        position_source=pos_row["position_source"],
                        raw_data_provider=pos_row["raw_data_provider"],
                    )
                    track_points.append(state)
                except Exception as e_state_create:
                    module_logger.error(
                        f"Error creating CanonicalFlightState from DB row for ICAO {icao24_to_find}: {e_state_create}",
                        exc_info=True,
                    )
                    continue
        except sqlite3.Error as e:
            module_logger.error(f"DB error retrieving track for {icao24_to_find} on {target_date_utc.strftime('%Y-%m-%d')}: {e}", exc_info=True)
        finally:
            if cursor:
                cursor.close()
        return track_points

    def close_connection(self):
        if self._connection:
            try:
                self._connection.close()
                module_logger.info(
                    f"DB connection to {self._current_db_path} closed by request."
                )
            except sqlite3.Error as e:
                module_logger.error(
                    f"Error explicitly closing DB connection to {self._current_db_path}: {e}"
                )
            finally:
                self._connection = None
                self._current_db_path = None