//! WhatsApp conversation history — per-number message history and messaging window tracking.
use std::collections::HashMap;
use std::sync::Arc;
use tokio::sync::Mutex as TokioMutex;

use crate::chat::history::{load_chat_history, save_chat_history};
use crate::chat::transport::matrix::RoomConversation;

// ── Messaging window tracker ─────────────────────────────────────────────

/// Tracks the 24-hour messaging window per WhatsApp phone number.
///
/// Meta's Business API only permits free-form text messages within 24 hours of
/// the last *inbound* message from that user.  After that window expires, only
/// approved message templates may be sent.
///
/// Call [`record_message`] whenever an inbound message is received.  Before
/// sending a proactive notification, call [`is_within_window`] to choose
/// between free-form text and a template message.
pub struct MessagingWindowTracker {
    last_message: std::sync::Mutex<HashMap<String, std::time::Instant>>,
    #[allow(dead_code)] // Used by Meta provider path (is_within_window → send_notification)
    window_duration: std::time::Duration,
}

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

impl MessagingWindowTracker {
    /// Create a tracker with the standard 24-hour window.
    pub fn new() -> Self {
        Self {
            last_message: std::sync::Mutex::new(HashMap::new()),
            window_duration: std::time::Duration::from_secs(24 * 60 * 60),
        }
    }

    /// Create a tracker with a custom window duration (useful in tests).
    #[cfg(test)]
    pub(crate) fn with_duration(window_duration: std::time::Duration) -> Self {
        Self {
            last_message: std::sync::Mutex::new(HashMap::new()),
            window_duration,
        }
    }

    /// Record that `phone` sent an inbound message right now.
    pub fn record_message(&self, phone: &str) {
        self.last_message
            .lock()
            .unwrap()
            .insert(phone.to_string(), std::time::Instant::now());
    }

    /// Returns `true` when the last inbound message from `phone` arrived within
    /// the 24-hour window, meaning free-form replies are still permitted.
    #[allow(dead_code)] // Used by Meta provider path (send_notification)
    pub fn is_within_window(&self, phone: &str) -> bool {
        let map = self.last_message.lock().unwrap();
        match map.get(phone) {
            Some(&instant) => instant.elapsed() < self.window_duration,
            None => false,
        }
    }
}

// ── Conversation history persistence ─────────────────────────────────

/// Per-sender conversation history, keyed by phone number.
pub type WhatsAppConversationHistory = Arc<TokioMutex<HashMap<String, RoomConversation>>>;

/// Path to the persisted WhatsApp conversation history file.
const WHATSAPP_HISTORY_FILE: &str = ".huskies/whatsapp_history.json";

/// Load WhatsApp conversation history from disk.
pub fn load_whatsapp_history(project_root: &std::path::Path) -> HashMap<String, RoomConversation> {
    load_chat_history(project_root, WHATSAPP_HISTORY_FILE, "whatsapp")
}

/// Save WhatsApp conversation history to disk.
pub(super) fn save_whatsapp_history(
    project_root: &std::path::Path,
    history: &HashMap<String, RoomConversation>,
) {
    save_chat_history(project_root, WHATSAPP_HISTORY_FILE, "whatsapp", history);
}

// ── Tests ───────────────────────────────────────────────────────────────

#[cfg(test)]
mod tests {
    use super::*;
    use crate::chat::transport::matrix::{ConversationEntry, ConversationRole, RoomConversation};

    // ── MessagingWindowTracker ────────────────────────────────────────

    #[test]
    fn window_tracker_unknown_user_is_outside_window() {
        let tracker = MessagingWindowTracker::new();
        assert!(!tracker.is_within_window("15551234567"));
    }

    #[test]
    fn window_tracker_records_within_window() {
        let tracker = MessagingWindowTracker::new();
        tracker.record_message("15551234567");
        assert!(tracker.is_within_window("15551234567"));
    }

    #[test]
    fn window_tracker_expired_window_returns_false() {
        // Use a 1-nanosecond window so it expires immediately.
        let tracker = MessagingWindowTracker::with_duration(std::time::Duration::from_nanos(1));
        tracker.record_message("15551234567");
        // Sleep briefly to ensure the instant has elapsed.
        std::thread::sleep(std::time::Duration::from_millis(1));
        assert!(!tracker.is_within_window("15551234567"));
    }

    #[test]
    fn window_tracker_tracks_users_independently() {
        let tracker = MessagingWindowTracker::new();
        tracker.record_message("111");
        assert!(tracker.is_within_window("111"));
        assert!(!tracker.is_within_window("222"));
    }

    // ── WhatsApp history persistence tests ──────────────────────────────

    #[test]
    fn save_and_load_whatsapp_history_round_trips() {
        let tmp = tempfile::tempdir().unwrap();
        let sk = tmp.path().join(".huskies");
        std::fs::create_dir_all(&sk).unwrap();

        let mut history = HashMap::new();
        history.insert(
            "15551234567".to_string(),
            RoomConversation {
                session_id: Some("sess-abc".to_string()),
                entries: vec![
                    ConversationEntry {
                        role: ConversationRole::User,
                        sender: "15551234567".to_string(),
                        content: "hello".to_string(),
                    },
                    ConversationEntry {
                        role: ConversationRole::Assistant,
                        sender: String::new(),
                        content: "hi there!".to_string(),
                    },
                ],
                ..Default::default()
            },
        );

        save_whatsapp_history(tmp.path(), &history);
        let loaded = load_whatsapp_history(tmp.path());

        assert_eq!(loaded.len(), 1);
        let conv = loaded.get("15551234567").unwrap();
        assert_eq!(conv.session_id.as_deref(), Some("sess-abc"));
        assert_eq!(conv.entries.len(), 2);
        assert_eq!(conv.entries[0].content, "hello");
        assert_eq!(conv.entries[1].content, "hi there!");
    }

    #[test]
    fn load_whatsapp_history_returns_empty_when_file_missing() {
        let tmp = tempfile::tempdir().unwrap();
        let history = load_whatsapp_history(tmp.path());
        assert!(history.is_empty());
    }

    #[test]
    fn load_whatsapp_history_returns_empty_on_invalid_json() {
        let tmp = tempfile::tempdir().unwrap();
        let sk = tmp.path().join(".huskies");
        std::fs::create_dir_all(&sk).unwrap();
        std::fs::write(sk.join("whatsapp_history.json"), "not json {{{").unwrap();
        let history = load_whatsapp_history(tmp.path());
        assert!(history.is_empty());
    }

    #[test]
    fn save_whatsapp_history_preserves_multiple_senders() {
        let tmp = tempfile::tempdir().unwrap();
        let sk = tmp.path().join(".huskies");
        std::fs::create_dir_all(&sk).unwrap();

        let mut history = HashMap::new();
        history.insert(
            "111".to_string(),
            RoomConversation {
                session_id: None,
                entries: vec![ConversationEntry {
                    role: ConversationRole::User,
                    sender: "111".to_string(),
                    content: "msg1".to_string(),
                }],
                ..Default::default()
            },
        );
        history.insert(
            "222".to_string(),
            RoomConversation {
                session_id: Some("sess-222".to_string()),
                entries: vec![ConversationEntry {
                    role: ConversationRole::User,
                    sender: "222".to_string(),
                    content: "msg2".to_string(),
                }],
                ..Default::default()
            },
        );

        save_whatsapp_history(tmp.path(), &history);
        let loaded = load_whatsapp_history(tmp.path());

        assert_eq!(loaded.len(), 2);
        assert!(loaded.contains_key("111"));
        assert!(loaded.contains_key("222"));
        assert_eq!(loaded["222"].session_id.as_deref(), Some("sess-222"));
    }
}
