//! Idle Session Detection
//!
//! This module provides utilities for detecting idle sessions and emitting
//! SessionIdle events after a period of inactivity.

use std::collections::HashMap;
use std::sync::Arc;
use std::time::{Duration, Instant};

use crate::sharing::bus::SharingBus;
use dirigent_protocol::{Event, Message, TurnCompleteTrigger};
use tokio::sync::{broadcast, Mutex};
use tracing::{info, trace};
use uuid::Uuid;

/// Duration of notification silence before emitting SessionIdle
/// 300ms is a good balance between responsiveness and not being too aggressive
pub const IDLE_THRESHOLD: Duration = Duration::from_millis(300);

/// Data needed to emit a deferred MessageCompleted event
/// Stored until SessionIdle is ready to emit, ensuring all notifications are processed
#[derive(Debug, Clone)]
pub struct PendingCompletion {
    pub connector_id: String,
    pub message: Message,
}

/// Per-session state for activity tracking and idle detection
#[derive(Debug)]
pub struct SessionState {
    /// Last time a notification was received for this session
    last_activity: Instant,
    /// True after JSON-RPC response received, false after SessionIdle emitted
    awaiting_idle: bool,
    /// Pending MessageCompleted to emit when SessionIdle fires
    /// This delays MessageCompleted until all notifications are processed
    pub pending_completion: Option<PendingCompletion>,
}

impl SessionState {
    /// Create a new session state
    pub fn new(_id: String) -> Self {
        Self {
            last_activity: Instant::now(),
            awaiting_idle: false,
            pending_completion: None,
        }
    }

    /// Update activity timestamp (call when notification received)
    pub fn touch(&mut self) {
        self.last_activity = Instant::now();
    }

    /// Mark session as awaiting idle emission
    pub fn mark_awaiting_idle(&mut self) {
        self.awaiting_idle = true;
        self.last_activity = Instant::now();
    }

    /// Clear awaiting idle flag (after SessionIdle emitted)
    pub fn clear_awaiting_idle(&mut self) {
        self.awaiting_idle = false;
    }

    /// Check if session should emit SessionIdle
    pub fn should_emit_idle(&self, threshold: Duration) -> bool {
        self.awaiting_idle && self.last_activity.elapsed() >= threshold
    }

    /// Get time since last activity
    pub fn elapsed(&self) -> Duration {
        self.last_activity.elapsed()
    }

    /// Check if session is awaiting idle
    pub fn is_awaiting_idle(&self) -> bool {
        self.awaiting_idle
    }
}

/// Check all sessions and emit SessionIdle for those with no recent activity
///
/// For sessions with pending MessageCompleted, emits MessageCompleted BEFORE SessionIdle.
/// This ensures all notifications are processed before finalization signals are sent.
///
/// Returns the list of session IDs that transitioned to idle.
pub async fn check_idle_sessions(
    session_states: &Arc<Mutex<HashMap<String, SessionState>>>,
    events_tx: &broadcast::Sender<Event>,
    sharing_bus: &Arc<SharingBus>,
    connector_uid: Option<Uuid>,
    connector_id: &str,
) -> Vec<String> {
    let now = Instant::now();
    let mut sessions_to_idle: Vec<(String, Option<PendingCompletion>)> = Vec::new();

    // Collect sessions that should emit idle, along with any pending completions
    {
        let mut states = session_states.lock().await;
        for (session_id, state) in states.iter_mut() {
            // Add trace logging for debugging idle detection
            if state.awaiting_idle {
                let elapsed = now.duration_since(state.last_activity);
                trace!(
                    session_id = %session_id,
                    elapsed_ms = elapsed.as_millis(),
                    threshold_ms = IDLE_THRESHOLD.as_millis(),
                    has_pending = state.pending_completion.is_some(),
                    "Checking session idle status"
                );
            }

            if state.should_emit_idle(IDLE_THRESHOLD) {
                // Take pending_completion (moves ownership out)
                let pending = state.pending_completion.take();
                sessions_to_idle.push((session_id.clone(), pending));
            }
        }
    }

    let mut idled_sessions = Vec::with_capacity(sessions_to_idle.len());

    // Emit MessageCompleted (if pending) then TurnComplete then SessionIdle for each qualifying session
    for (session_id, pending_completion) in sessions_to_idle {
        // IMPORTANT: Emit MessageCompleted BEFORE TurnComplete and SessionIdle
        // This ensures archivist receives complete message data before finalization
        if let Some(pending) = pending_completion {
            info!(
                session_id = %session_id,
                message_id = %pending.message.id,
                "Emitting deferred MessageCompleted after {:?} of inactivity",
                IDLE_THRESHOLD
            );

            let completed_event = Event::MessageCompleted {
                connector_id: pending.connector_id.clone(),
                message: pending.message.clone(),
            };
            let bus_event = dirigent_protocol::streaming::BusEvent::from_connector_event(
                completed_event.clone(),
                connector_uid,
                connector_id.to_string(),
            );
            sharing_bus.publish(bus_event).await;
            let _ = events_tx.send(completed_event);

            // Also emit TurnComplete with ResponseReceived trigger
            // This signals that the turn is truly complete (ACP response received)
            info!(
                session_id = %session_id,
                message_id = %pending.message.id,
                "Emitting TurnComplete with ResponseReceived trigger",
            );

            let turn_event = Event::TurnComplete {
                connector_id: pending.connector_id,
                session_id: session_id.clone(),
                message_id: pending.message.id,
                trigger: TurnCompleteTrigger::ResponseReceived,
            };
            let bus_event = dirigent_protocol::streaming::BusEvent::from_connector_event(
                turn_event.clone(),
                connector_uid,
                connector_id.to_string(),
            );
            sharing_bus.publish(bus_event).await;
            let _ = events_tx.send(turn_event);
        }

        // Then emit SessionIdle
        info!(
            session_id = %session_id,
            "Emitting SessionIdle after {:?} of inactivity",
            IDLE_THRESHOLD
        );

        let idle_event = Event::SessionIdle {
            connector_id: connector_id.to_string(),
            session_id: session_id.clone(),
        };
        let bus_event = dirigent_protocol::streaming::BusEvent::from_connector_event(
            idle_event.clone(),
            connector_uid,
            connector_id.to_string(),
        );
        sharing_bus.publish(bus_event).await;
        let _ = events_tx.send(idle_event);

        // Clear the awaiting_idle flag
        {
            let mut states = session_states.lock().await;
            if let Some(state) = states.get_mut(&session_id) {
                state.clear_awaiting_idle();
            }
        }

        idled_sessions.push(session_id);
    }

    idled_sessions
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_session_state_new() {
        let state = SessionState::new("test".to_string());
        assert!(!state.is_awaiting_idle());
        assert!(state.pending_completion.is_none());
    }

    #[test]
    fn test_session_state_touch() {
        let mut state = SessionState::new("test".to_string());
        let before = state.elapsed();
        std::thread::sleep(Duration::from_millis(10));
        state.touch();
        let after = state.elapsed();
        assert!(after < before);
    }

    #[test]
    fn test_session_state_awaiting_idle() {
        let mut state = SessionState::new("test".to_string());
        assert!(!state.is_awaiting_idle());

        state.mark_awaiting_idle();
        assert!(state.is_awaiting_idle());

        state.clear_awaiting_idle();
        assert!(!state.is_awaiting_idle());
    }

    #[test]
    fn test_session_state_should_emit_idle() {
        let mut state = SessionState::new("test".to_string());

        // Not awaiting idle - should not emit
        assert!(!state.should_emit_idle(Duration::ZERO));

        // Awaiting idle but not enough time passed
        state.mark_awaiting_idle();
        assert!(!state.should_emit_idle(Duration::from_secs(10)));

        // Awaiting idle with zero threshold - should emit immediately
        assert!(state.should_emit_idle(Duration::ZERO));
    }
}
