use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use tauri::AppHandle;
use tauri_plugin_store::StoreExt;

const SESSIONS_STORE_KEY: &str = "sessions";

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavedSession {
    pub id: String,
    pub name: String,
    pub created_at: DateTime<Utc>,
    pub last_activity_at: DateTime<Utc>,
    pub working_directory: String,
    pub message_count: usize,
    pub preview: String, // First ~100 chars of conversation for preview
    pub messages: Vec<SavedMessage>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SavedMessage {
    pub id: String,
    #[serde(rename = "type")]
    pub message_type: String,
    pub content: String,
    pub timestamp: DateTime<Utc>,
    pub tool_name: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SessionListItem {
    pub id: String,
    pub name: String,
    pub created_at: DateTime<Utc>,
    pub last_activity_at: DateTime<Utc>,
    pub working_directory: String,
    pub message_count: usize,
    pub preview: String,
}

impl From<&SavedSession> for SessionListItem {
    fn from(session: &SavedSession) -> Self {
        SessionListItem {
            id: session.id.clone(),
            name: session.name.clone(),
            created_at: session.created_at,
            last_activity_at: session.last_activity_at,
            working_directory: session.working_directory.clone(),
            message_count: session.message_count,
            preview: session.preview.clone(),
        }
    }
}

fn load_all_sessions(app: &AppHandle) -> Result<Vec<SavedSession>, String> {
    let store = app
        .store("hikari-sessions.json")
        .map_err(|e| e.to_string())?;

    match store.get(SESSIONS_STORE_KEY) {
        Some(value) => serde_json::from_value(value.clone()).map_err(|e| e.to_string()),
        None => Ok(Vec::new()),
    }
}

fn save_all_sessions(app: &AppHandle, sessions: &[SavedSession]) -> Result<(), String> {
    let store = app
        .store("hikari-sessions.json")
        .map_err(|e| e.to_string())?;

    let value = serde_json::to_value(sessions).map_err(|e| e.to_string())?;
    store.set(SESSIONS_STORE_KEY, value);
    store.save().map_err(|e| e.to_string())?;

    Ok(())
}

#[tauri::command]
pub async fn list_sessions(app: AppHandle) -> Result<Vec<SessionListItem>, String> {
    let sessions = load_all_sessions(&app)?;
    let mut items: Vec<SessionListItem> = sessions.iter().map(SessionListItem::from).collect();

    // Sort by last activity, most recent first
    items.sort_by(|a, b| b.last_activity_at.cmp(&a.last_activity_at));

    Ok(items)
}

#[tauri::command]
pub async fn save_session(app: AppHandle, session: SavedSession) -> Result<(), String> {
    let mut sessions = load_all_sessions(&app)?;

    // Update existing or add new
    if let Some(existing) = sessions.iter_mut().find(|s| s.id == session.id) {
        *existing = session;
    } else {
        sessions.push(session);
    }

    save_all_sessions(&app, &sessions)
}

#[tauri::command]
pub async fn load_session(app: AppHandle, session_id: String) -> Result<Option<SavedSession>, String> {
    let sessions = load_all_sessions(&app)?;
    Ok(sessions.into_iter().find(|s| s.id == session_id))
}

#[tauri::command]
pub async fn delete_session(app: AppHandle, session_id: String) -> Result<(), String> {
    let mut sessions = load_all_sessions(&app)?;
    sessions.retain(|s| s.id != session_id);
    save_all_sessions(&app, &sessions)
}

#[tauri::command]
pub async fn search_sessions(app: AppHandle, query: String) -> Result<Vec<SessionListItem>, String> {
    let sessions = load_all_sessions(&app)?;
    let query_lower = query.to_lowercase();

    let mut matching: Vec<SessionListItem> = sessions
        .iter()
        .filter(|s| {
            s.name.to_lowercase().contains(&query_lower)
                || s.preview.to_lowercase().contains(&query_lower)
                || s.working_directory.to_lowercase().contains(&query_lower)
                || s.messages
                    .iter()
                    .any(|m| m.content.to_lowercase().contains(&query_lower))
        })
        .map(SessionListItem::from)
        .collect();

    // Sort by last activity, most recent first
    matching.sort_by(|a, b| b.last_activity_at.cmp(&a.last_activity_at));

    Ok(matching)
}

#[tauri::command]
pub async fn clear_all_sessions(app: AppHandle) -> Result<(), String> {
    save_all_sessions(&app, &[])
}

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

    fn create_test_session(id: &str, name: &str) -> SavedSession {
        SavedSession {
            id: id.to_string(),
            name: name.to_string(),
            created_at: Utc::now(),
            last_activity_at: Utc::now(),
            working_directory: "/home/test".to_string(),
            message_count: 5,
            preview: "Hello world".to_string(),
            messages: vec![],
        }
    }

    fn create_test_message(id: &str, content: &str, msg_type: &str) -> SavedMessage {
        SavedMessage {
            id: id.to_string(),
            message_type: msg_type.to_string(),
            content: content.to_string(),
            timestamp: Utc::now(),
            tool_name: None,
        }
    }

    #[test]
    fn test_session_list_item_from_saved_session() {
        let session = SavedSession {
            id: "test-id".to_string(),
            name: "Test Session".to_string(),
            created_at: Utc::now(),
            last_activity_at: Utc::now(),
            working_directory: "/home/test".to_string(),
            message_count: 5,
            preview: "Hello world".to_string(),
            messages: vec![],
        };

        let item = SessionListItem::from(&session);
        assert_eq!(item.id, "test-id");
        assert_eq!(item.name, "Test Session");
        assert_eq!(item.message_count, 5);
    }

    #[test]
    fn test_session_list_item_preserves_all_fields() {
        let created = Utc.with_ymd_and_hms(2024, 1, 15, 10, 30, 0).unwrap();
        let last_activity = Utc.with_ymd_and_hms(2024, 1, 15, 14, 45, 0).unwrap();

        let session = SavedSession {
            id: "sess-123".to_string(),
            name: "My Chat".to_string(),
            created_at: created,
            last_activity_at: last_activity,
            working_directory: "/home/naomi/project".to_string(),
            message_count: 42,
            preview: "What is the meaning of life?".to_string(),
            messages: vec![],
        };

        let item = SessionListItem::from(&session);

        assert_eq!(item.id, "sess-123");
        assert_eq!(item.name, "My Chat");
        assert_eq!(item.created_at, created);
        assert_eq!(item.last_activity_at, last_activity);
        assert_eq!(item.working_directory, "/home/naomi/project");
        assert_eq!(item.message_count, 42);
        assert_eq!(item.preview, "What is the meaning of life?");
    }

    #[test]
    fn test_saved_session_serialization() {
        let session = create_test_session("test-1", "Test Session");
        let json = serde_json::to_string(&session).expect("Failed to serialize");
        let parsed: SavedSession = serde_json::from_str(&json).expect("Failed to deserialize");

        assert_eq!(parsed.id, session.id);
        assert_eq!(parsed.name, session.name);
        assert_eq!(parsed.working_directory, session.working_directory);
    }

    #[test]
    fn test_saved_message_serialization() {
        let message = create_test_message("msg-1", "Hello!", "user");
        let json = serde_json::to_string(&message).expect("Failed to serialize");
        let parsed: SavedMessage = serde_json::from_str(&json).expect("Failed to deserialize");

        assert_eq!(parsed.id, message.id);
        assert_eq!(parsed.content, message.content);
        assert_eq!(parsed.message_type, "user");
    }

    #[test]
    fn test_saved_message_with_tool_name() {
        let message = SavedMessage {
            id: "msg-tool-1".to_string(),
            message_type: "tool".to_string(),
            content: "File read successfully".to_string(),
            timestamp: Utc::now(),
            tool_name: Some("Read".to_string()),
        };

        let json = serde_json::to_string(&message).expect("Failed to serialize");
        let parsed: SavedMessage = serde_json::from_str(&json).expect("Failed to deserialize");

        assert_eq!(parsed.tool_name, Some("Read".to_string()));
    }

    #[test]
    fn test_session_with_messages_serialization() {
        let mut session = create_test_session("sess-full", "Full Session");
        session.messages = vec![
            create_test_message("msg-1", "Hello!", "user"),
            create_test_message("msg-2", "Hi there!", "assistant"),
            create_test_message("msg-3", "Read file", "tool"),
        ];
        session.message_count = 3;

        let json = serde_json::to_string(&session).expect("Failed to serialize");
        let parsed: SavedSession = serde_json::from_str(&json).expect("Failed to deserialize");

        assert_eq!(parsed.messages.len(), 3);
        assert_eq!(parsed.messages[0].content, "Hello!");
        assert_eq!(parsed.messages[1].message_type, "assistant");
        assert_eq!(parsed.messages[2].message_type, "tool");
    }

    #[test]
    fn test_session_list_item_serialization() {
        let item = SessionListItem {
            id: "list-item-1".to_string(),
            name: "Quick Chat".to_string(),
            created_at: Utc::now(),
            last_activity_at: Utc::now(),
            working_directory: "/tmp".to_string(),
            message_count: 10,
            preview: "Short preview...".to_string(),
        };

        let json = serde_json::to_string(&item).expect("Failed to serialize");
        let parsed: SessionListItem = serde_json::from_str(&json).expect("Failed to deserialize");

        assert_eq!(parsed.id, item.id);
        assert_eq!(parsed.name, item.name);
        assert_eq!(parsed.preview, item.preview);
    }

    #[test]
    fn test_message_type_field_rename() {
        // The message_type field is renamed to "type" in JSON
        let message = create_test_message("msg-1", "Test", "assistant");
        let json = serde_json::to_string(&message).expect("Failed to serialize");

        assert!(json.contains("\"type\":"));
        assert!(!json.contains("\"message_type\":"));
    }

    #[test]
    fn test_session_default_empty_messages() {
        let session = SavedSession {
            id: "empty".to_string(),
            name: "Empty".to_string(),
            created_at: Utc::now(),
            last_activity_at: Utc::now(),
            working_directory: "/".to_string(),
            message_count: 0,
            preview: "".to_string(),
            messages: vec![],
        };

        assert!(session.messages.is_empty());
        assert_eq!(session.message_count, 0);
    }

    #[test]
    #[allow(clippy::useless_vec)]
    fn test_session_sorting_by_activity() {
        let old_time = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
        let new_time = Utc.with_ymd_and_hms(2024, 6, 15, 12, 0, 0).unwrap();

        let mut sessions = vec![
            SessionListItem {
                id: "old".to_string(),
                name: "Old Session".to_string(),
                created_at: old_time,
                last_activity_at: old_time,
                working_directory: "/old".to_string(),
                message_count: 1,
                preview: "Old".to_string(),
            },
            SessionListItem {
                id: "new".to_string(),
                name: "New Session".to_string(),
                created_at: new_time,
                last_activity_at: new_time,
                working_directory: "/new".to_string(),
                message_count: 1,
                preview: "New".to_string(),
            },
        ];

        // Sort by last activity, most recent first (mimics list_sessions behavior)
        sessions.sort_by(|a, b| b.last_activity_at.cmp(&a.last_activity_at));

        assert_eq!(sessions[0].id, "new");
        assert_eq!(sessions[1].id, "old");
    }

    #[test]
    fn test_session_clone() {
        let original = create_test_session("clone-test", "Clone Test");
        let cloned = original.clone();

        assert_eq!(original.id, cloned.id);
        assert_eq!(original.name, cloned.name);
    }

    #[test]
    fn test_message_clone() {
        let original = create_test_message("msg-clone", "Content", "user");
        let cloned = original.clone();

        assert_eq!(original.id, cloned.id);
        assert_eq!(original.content, cloned.content);
    }
}
