use crate::imap::ImapClient;
use crate::types::MailServerConfig;
use lazy_static::lazy_static;
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex;

lazy_static! {
    /// A global map of active IMAP sessions, keyed by email address.
    /// Each session is wrapped in an Arc<Mutex> to allow safe concurrent access (though IMAP is largely sequential).
    static ref SESSIONS: Arc<Mutex<HashMap<String, Arc<Mutex<ImapClient>>>>> = Arc::new(Mutex::new(HashMap::new()));
}

/// Retrieves an existing active session or creates a new one.
/// If an existing session is found but disconnected, it attempts to reconnect.
pub async fn get_client(
    config: MailServerConfig,
    email: String,
    password: String,
) -> Result<Arc<Mutex<ImapClient>>, crate::types::MailError> {
    // Atomic get-or-insert to avoid race conditions on startup
    let (client_arc, is_new) = {
        let mut sessions = SESSIONS.lock().await;
        if let Some(arc) = sessions.get(&email) {
            (arc.clone(), false)
        } else {
            let client = ImapClient::new(config, email.clone());
            let arc = Arc::new(Mutex::new(client));
            sessions.insert(email.clone(), arc.clone());
            (arc, true)
        }
    };

    let mut client = client_arc.lock().await;

    if is_new {
        tracing::debug!(
            "SyncManager: Establishing new IMAP connection for {}...",
            email
        );
        if let Err(e) = tokio::time::timeout(
            std::time::Duration::from_secs(20),
            client.connect(&password),
        )
        .await
        .unwrap_or_else(|_| Err("Connection timeout".into()))
        {
            let err_msg = e.to_string();
            // Critical cleanup: Remove failed shell so it can be retried later
            {
                let mut sessions = SESSIONS.lock().await;
                sessions.remove(&email);
            }
            if err_msg.to_lowercase().contains("auth")
                || err_msg.to_lowercase().contains("login")
                || err_msg.to_lowercase().contains("credentials")
            {
                return Err(crate::types::MailError::Auth(err_msg));
            } else {
                return Err(crate::types::MailError::Network(err_msg));
            }
        }
    } else {
        // Double-check connection health
        if !tokio::time::timeout(std::time::Duration::from_secs(5), client.keep_alive())
            .await
            .unwrap_or(false)
        {
            tracing::debug!(
                "SyncManager: Connection for {} is dead, attempting reconnect...",
                email
            );
            if let Err(e) = tokio::time::timeout(
                std::time::Duration::from_secs(15),
                client.reconnect(&password),
            )
            .await
            .unwrap_or_else(|_| Err("Reconnect timeout".into()))
            {
                tracing::error!("SyncManager: Reconnect failed for {}: {}", email, e);
                // Remove the stale entry so the next call creates a fresh connection
                // instead of looping on keep_alive() against a broken session.
                drop(client);
                let mut sessions = SESSIONS.lock().await;
                sessions.remove(&email);
                return Err(crate::types::MailError::Network(e.to_string()));
            }
        }
    }

    Ok(client_arc.clone())
}

/// Disconnects and removes a session from the manager.
pub async fn logout(email: &str) {
    let mut sessions = SESSIONS.lock().await;
    if let Some(client_arc) = sessions.remove(email) {
        let mut client = client_arc.lock().await;
        let _ = client.logout().await;
        tracing::debug!("Logged out session for {}", email);
    }
}

/// Checks if a session exists and is alive for the given email.
pub async fn is_connected(email: &str) -> bool {
    let sessions = SESSIONS.lock().await;
    if let Some(client_arc) = sessions.get(email) {
        let mut client = client_arc.lock().await;
        return client.keep_alive().await;
    }
    false
}
