//! Event mapper for mapping game events to video timestamps.

use chrono::{DateTime, Duration, Utc};
use tracing::debug;

/// Event mapper that tracks recording start time and maps events to video timestamps.
pub struct EventMapper {
    /// Recording start time.
    start_time: Option<DateTime<Utc>>,
    /// Game start time (from game event).
    game_start_time: Option<DateTime<Utc>>,
    /// Synchronizer for handling time drift.
    synchronizer: EventSynchronizer,
}

impl EventMapper {
    /// Create a new event mapper.
    pub fn new() -> Self {
        Self {
            start_time: None,
            game_start_time: None,
            synchronizer: EventSynchronizer::new(),
        }
    }

    /// Start the mapper (recording started).
    pub fn start(&mut self) {
        self.start_time = Some(Utc::now());
        self.synchronizer.reset();
        debug!("Event mapper started at {:?}", self.start_time);
    }

    /// Stop the mapper (recording stopped).
    pub fn stop(&mut self) {
        self.start_time = None;
        self.game_start_time = None;
        self.synchronizer.reset();
        debug!("Event mapper stopped");
    }

    /// Check if the mapper is active.
    pub fn is_active(&self) -> bool {
        self.start_time.is_some()
    }

    /// Map an event to video and game timestamps.
    pub fn map_event(&self) -> Option<(Duration, Option<Duration>)> {
        let start_time = self.start_time?;
        let now = Utc::now();

        // Calculate video timestamp (time since recording started)
        let video_timestamp = now - start_time;

        // Calculate game timestamp if we have game start time
        let game_timestamp = self.game_start_time.map(|game_start| {
            let raw_ts = now - game_start;
            // Apply drift correction if synchronizer has data
            self.synchronizer.adjust_game_timestamp(raw_ts)
        });

        Some((video_timestamp, game_timestamp))
    }

    /// Handle a game event and return mapped timestamps.
    /// event_type is used to detect game start for timestamp tracking.
    pub fn handle_event(&mut self, event_type: &str) -> Option<(Duration, Option<Duration>)> {
        if !self.is_active() {
            return None;
        }

        // Track game start time
        if event_type == crate::lqp::EVENT_TYPE_GAME_START {
            self.game_start_time = Some(Utc::now());
            debug!("Game start time recorded: {:?}", self.game_start_time);
        }

        let result = self.map_event();

        // Add sync point if we have both timestamps
        if let Some((video_ts, Some(game_ts))) = result {
            self.synchronizer.add_sync_point(video_ts, game_ts);
        }

        result
    }

    /// Get the current video timestamp.
    pub fn current_video_timestamp(&self) -> Option<Duration> {
        self.start_time.map(|start| Utc::now() - start)
    }

    /// Get the current game timestamp.
    pub fn current_game_timestamp(&self) -> Option<Duration> {
        self.game_start_time.map(|start| {
            let raw_ts = Utc::now() - start;
            self.synchronizer.adjust_game_timestamp(raw_ts)
        })
    }

    /// Get the recording duration so far.
    pub fn recording_duration(&self) -> Option<Duration> {
        self.current_video_timestamp()
    }

    /// Check if time drift is within acceptable bounds.
    pub fn is_time_sync_healthy(&self) -> bool {
        self.synchronizer.is_drift_acceptable()
    }

    /// Get the current time drift, if any.
    pub fn time_drift(&self) -> Option<Duration> {
        self.synchronizer.calculate_drift()
    }

    /// Reset the mapper.
    pub fn reset(&mut self) {
        self.start_time = None;
        self.game_start_time = None;
        self.synchronizer.reset();
        debug!("Event mapper reset");
    }
}

impl Default for EventMapper {
    fn default() -> Self {
        Self::new()
    }
}

/// Event synchronizer for keeping video and game time in sync.
///
/// This handles cases where the game time might drift from real time,
/// such as when the game pauses or lags.
pub struct EventSynchronizer {
    /// Known sync points (video timestamp, game timestamp).
    sync_points: Vec<(Duration, Duration)>,
    /// Maximum allowed drift in seconds.
    max_drift_secs: f64,
}

impl EventSynchronizer {
    /// Create a new event synchronizer.
    pub fn new() -> Self {
        Self {
            sync_points: Vec::new(),
            max_drift_secs: 5.0,
        }
    }

    /// Add a sync point.
    pub fn add_sync_point(&mut self, video_ts: Duration, game_ts: Duration) {
        self.sync_points.push((video_ts, game_ts));

        // Keep only recent sync points
        if self.sync_points.len() > 100 {
            self.sync_points.remove(0);
        }
    }

    /// Calculate the drift between video and game time.
    pub fn calculate_drift(&self) -> Option<Duration> {
        if self.sync_points.len() < 2 {
            return None;
        }

        let first = self.sync_points.first()?;
        let last = self.sync_points.last()?;

        let video_diff = last.0 - first.0;
        let game_diff = last.1 - first.1;

        Some(video_diff - game_diff)
    }

    /// Check if the drift is within acceptable bounds.
    pub fn is_drift_acceptable(&self) -> bool {
        self.calculate_drift()
            .map(|drift| (drift.num_seconds().abs() as f64) < self.max_drift_secs)
            .unwrap_or(true)
    }

    /// Adjust a game timestamp based on known drift.
    pub fn adjust_game_timestamp(&self, game_ts: Duration) -> Duration {
        if let Some(drift) = self.calculate_drift() {
            game_ts + drift
        } else {
            game_ts
        }
    }

    /// Reset the synchronizer.
    pub fn reset(&mut self) {
        self.sync_points.clear();
    }
}

impl Default for EventSynchronizer {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::thread::sleep;
    use std::time::Duration as StdDuration;

    #[test]
    fn test_event_mapper_start_stop() {
        let mut mapper = EventMapper::new();

        assert!(!mapper.is_active());

        mapper.start();
        assert!(mapper.is_active());

        mapper.stop();
        assert!(!mapper.is_active());
    }

    #[test]
    fn test_event_mapper_timestamps() {
        let mut mapper = EventMapper::new();
        mapper.start();

        sleep(StdDuration::from_millis(100));

        let ts = mapper.current_video_timestamp().unwrap();
        assert!(ts.num_milliseconds() >= 100);
    }

    #[test]
    fn test_event_synchronizer() {
        let mut sync = EventSynchronizer::new();

        sync.add_sync_point(Duration::seconds(0), Duration::seconds(0));
        sync.add_sync_point(Duration::seconds(10), Duration::seconds(10));

        assert!(sync.is_drift_acceptable());

        // Add a drift
        sync.add_sync_point(Duration::seconds(20), Duration::seconds(18));

        let drift = sync.calculate_drift().unwrap();
        assert_eq!(drift.num_seconds(), 2);
    }
}
