//! Integration tests for SqliteStorage.

use super::*;
use chrono::{DateTime, Utc};
use fyi_core::{Event, EventType};
use std::path::PathBuf;

fn temp_db() -> Result<(PathBuf, SqliteStorage), String> {
    let path = std::env::temp_dir().join(format!(
        "fyi-sqlite-test-{}-{:?}-{}.db",
        std::process::id(),
        std::thread::current().id(),
        std::time::SystemTime::now()
            .duration_since(std::time::UNIX_EPOCH)
            .map_err(|e| e.to_string())?
            .as_nanos()
    ));
    let storage = SqliteStorage::open(&path).map_err(|e| e.to_string())?;
    Ok((path, storage))
}

/// Helper to create a DateTime<Utc> from milliseconds
fn ts(millis: i64) -> Result<DateTime<Utc>, String> {
    parse_timestamp_millis(millis).ok_or_else(|| format!("invalid timestamp: {}", millis))
}

/// Helper to parse EventType - infallible since unknown strings become Custom
fn parse_event_type(s: &str) -> EventType {
    s.parse::<EventType>()
        .unwrap_or_else(|e: std::convert::Infallible| match e {})
}

fn create_event(
    timestamp_millis: i64,
    session_id: &str,
    event_type: &str,
) -> Result<Event, String> {
    let event_type = parse_event_type(event_type);
    Ok(Event {
        id: None,
        machine_id: "test-machine".to_string(),
        timestamp: ts(timestamp_millis)?,
        event_type,
        session_id: session_id.to_string(),
        framework: None,
        tool_use_id: None,
        spawned_agent_id: None,
        tool_name: None,
        subagent_type: None,
        permission_mode: None,
        transcript_path: None,
        pid: None,
        cwd: None,
        model: None,
        tokens_input: None,
        tokens_output: None,
        tokens_cache_read: None,
        tokens_cache_creation: None,
        cost_usd: None,
        duration_ms: None,
        payload: None,
        metadata: None,
    })
}

// Time range filter tests
#[test]
fn test_query_after_ts() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    // Insert events at different timestamps
    storage
        .insert(&create_event(1000, "sess1", "SessionStart")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(3000, "sess1", "PostToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(4000, "sess1", "Stop")?)
        .map_err(|e| e.to_string())?;

    // Filter events after timestamp=2000 (should get timestamp=3000 and timestamp=4000)
    let query = EventQuery::new().with_after_ts(ts(2000)?).with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    // Events are returned in descending order by default
    assert_eq!(events[0].timestamp, ts(4000)?);
    assert_eq!(events[1].timestamp, ts(3000)?);
    Ok(())
}

#[test]
fn test_query_before_ts() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_event(1000, "sess1", "SessionStart")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(3000, "sess1", "PostToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(4000, "sess1", "Stop")?)
        .map_err(|e| e.to_string())?;

    // Filter events before timestamp=3000 (should get timestamp=1000 and timestamp=2000)
    let query = EventQuery::new().with_before_ts(ts(3000)?).with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    assert_eq!(events[0].timestamp, ts(2000)?);
    assert_eq!(events[1].timestamp, ts(1000)?);
    Ok(())
}

#[test]
fn test_query_between_ts() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_event(1000, "sess1", "SessionStart")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(3000, "sess1", "PostToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(4000, "sess1", "Stop")?)
        .map_err(|e| e.to_string())?;

    // Filter events between timestamp=1500 and timestamp=3500 (should get timestamp=2000 and timestamp=3000)
    let query = EventQuery::new()
        .with_between_ts(ts(1500)?, ts(3500)?)
        .with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    assert_eq!(events[0].timestamp, ts(3000)?);
    assert_eq!(events[1].timestamp, ts(2000)?);
    Ok(())
}

#[test]
fn test_query_time_range_combined_with_session() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_event(1000, "sess1", "SessionStart")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2500, "sess2", "SessionStart")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(3000, "sess1", "PostToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(4000, "sess2", "Stop")?)
        .map_err(|e| e.to_string())?;

    // Filter by session and time range
    let query = EventQuery::new()
        .with_session(Some("sess1".to_string()))
        .with_after_ts(ts(1500)?)
        .with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    assert!(events.iter().all(|e| e.session_id == "sess1"));
    assert!(
        events
            .iter()
            .all(|e| e.timestamp > ts(1500).ok().unwrap_or_default())
    );
    Ok(())
}

#[test]
fn test_query_time_range_combined_with_event_type() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_event(1000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(3000, "sess1", "PostToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(4000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;

    // Filter by event type and time range
    let query = EventQuery::new()
        .with_event_type(Some("PreToolUse".to_string()))
        .with_after_ts(ts(1500)?)
        .with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    assert!(events.iter().all(|e| e.event_type == EventType::PreToolUse));
    assert_eq!(events[0].timestamp, ts(4000)?);
    assert_eq!(events[1].timestamp, ts(2000)?);
    Ok(())
}

#[test]
fn test_query_after_id() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    let id1 = storage
        .insert(&create_event(1000, "sess1", "SessionStart")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(3000, "sess1", "PostToolUse")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(4000, "sess1", "Stop")?)
        .map_err(|e| e.to_string())?;

    // Filter events after id1, but only those with timestamp < 3500
    let query = EventQuery::new()
        .with_after_id(id1)
        .with_before_ts(ts(3500)?)
        .with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    // When after_id is set, results are ordered by ID ascending
    assert_eq!(events[0].timestamp, ts(2000)?);
    assert_eq!(events[1].timestamp, ts(3000)?);
    Ok(())
}

#[test]
fn test_query_empty_time_range() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_event(1000, "sess1", "SessionStart")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "PreToolUse")?)
        .map_err(|e| e.to_string())?;

    // Filter with impossible range (after > before)
    let query = EventQuery::new()
        .with_between_ts(ts(5000)?, ts(500)?)
        .with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 0);
    Ok(())
}

#[test]
fn test_query_after_id_orders_by_id_not_timestamp() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    // Insert events with timestamps in non-sequential order relative to IDs
    // This tests that after_id queries order by ID, not timestamp
    let id1 = storage
        .insert(&create_event(3000, "sess1", "Event1")?)
        .map_err(|e| e.to_string())?;
    let id2 = storage
        .insert(&create_event(1000, "sess1", "Event2")?)
        .map_err(|e| e.to_string())?;
    let id3 = storage
        .insert(&create_event(2000, "sess1", "Event3")?)
        .map_err(|e| e.to_string())?;

    // Query after id1 - should return events 2 and 3 in ID order
    let query = EventQuery::new().with_after_id(id1).with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    // Events should be ordered by ID ascending, not timestamp
    assert_eq!(events[0].id, Some(id2));
    assert_eq!(events[0].timestamp, ts(1000)?); // Lower timestamp but higher ID
    assert_eq!(events[1].id, Some(id3));
    assert_eq!(events[1].timestamp, ts(2000)?);
    Ok(())
}

#[test]
fn test_query_order_direction() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_event(1000, "sess1", "Event1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(2000, "sess1", "Event2")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_event(3000, "sess1", "Event3")?)
        .map_err(|e| e.to_string())?;

    // Default order is Desc (newest first)
    let query = EventQuery::new().with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;
    assert_eq!(events[0].timestamp, ts(3000)?);
    assert_eq!(events[2].timestamp, ts(1000)?);

    // Explicit Asc order (oldest first)
    let query = EventQuery::new().with_direction(Order::Asc).with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;
    assert_eq!(events[0].timestamp, ts(1000)?);
    assert_eq!(events[2].timestamp, ts(3000)?);
    Ok(())
}

#[test]
fn test_query_after_id_with_explicit_desc_direction() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    // Insert events with timestamps in non-sequential order relative to IDs
    let id1 = storage
        .insert(&create_event(3000, "sess1", "Event1")?)
        .map_err(|e| e.to_string())?;
    let id2 = storage
        .insert(&create_event(1000, "sess1", "Event2")?)
        .map_err(|e| e.to_string())?;
    let id3 = storage
        .insert(&create_event(2000, "sess1", "Event3")?)
        .map_err(|e| e.to_string())?;

    // Query after id1 with explicit DESC direction
    // Should return events in ID descending order (id3, id2)
    let query = EventQuery::new()
        .with_after_id(id1)
        .with_direction(Order::Desc)
        .with_limit(100);
    let events = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(events.len(), 2);
    // Events should be ordered by ID descending, not timestamp
    assert_eq!(events[0].id, Some(id3)); // Higher ID first
    assert_eq!(events[1].id, Some(id2)); // Lower ID second
    Ok(())
}

// API request event tests (using unified Event with EventType::ApiRequest)

fn create_api_request_event(timestamp_millis: i64, session_id: &str) -> Result<Event, String> {
    Ok(Event {
        id: None,
        machine_id: "test-machine".to_string(),
        timestamp: ts(timestamp_millis)?,
        event_type: EventType::ApiRequest,
        session_id: session_id.to_string(),
        framework: Some("claude".to_string()),
        tool_use_id: None,
        spawned_agent_id: None,
        tool_name: None,
        subagent_type: None,
        permission_mode: None,
        transcript_path: None,
        pid: None,
        cwd: None,
        model: Some("claude-sonnet".to_string()),
        tokens_input: Some(100),
        tokens_output: Some(50),
        tokens_cache_read: None,
        tokens_cache_creation: None,
        cost_usd: Some(0.001),
        duration_ms: Some(100),
        payload: None,
        metadata: None,
    })
}

#[test]
fn test_api_request_query_basic() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_api_request_event(1000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(2000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(3000, "sess2")?)
        .map_err(|e| e.to_string())?;

    // Query all API requests using api_requests_only filter
    let query = EventQuery::new().api_requests_only().with_limit(100);
    let requests = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(requests.len(), 3);
    // Default order is Desc (newest first)
    assert_eq!(requests[0].timestamp, ts(3000)?);
    assert_eq!(requests[1].timestamp, ts(2000)?);
    assert_eq!(requests[2].timestamp, ts(1000)?);
    Ok(())
}

#[test]
fn test_api_request_query_with_session_filter() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_api_request_event(1000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(2000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(3000, "sess2")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(4000, "sess2")?)
        .map_err(|e| e.to_string())?;

    // Filter by session
    let query = EventQuery::new()
        .api_requests_only()
        .with_session(Some("sess1".to_string()))
        .with_limit(100);
    let requests = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(requests.len(), 2);
    assert!(requests.iter().all(|r| r.session_id == "sess1"));
    assert_eq!(requests[0].timestamp, ts(2000)?);
    assert_eq!(requests[1].timestamp, ts(1000)?);
    Ok(())
}

#[test]
fn test_api_request_query_after_id() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    let id1 = storage
        .insert(&create_api_request_event(3000, "sess1")?)
        .map_err(|e| e.to_string())?;
    let id2 = storage
        .insert(&create_api_request_event(1000, "sess1")?)
        .map_err(|e| e.to_string())?;
    let id3 = storage
        .insert(&create_api_request_event(2000, "sess1")?)
        .map_err(|e| e.to_string())?;

    // Query after id1 - should return requests 2 and 3 in ID order (ascending)
    let query = EventQuery::new()
        .api_requests_only()
        .with_after_id(id1)
        .with_limit(100);
    let requests = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(requests.len(), 2);
    // Requests should be ordered by ID ascending (default when after_id is set)
    assert_eq!(requests[0].id, Some(id2));
    assert_eq!(requests[1].id, Some(id3));
    Ok(())
}

#[test]
fn test_api_request_query_after_id_with_explicit_desc() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    let id1 = storage
        .insert(&create_api_request_event(3000, "sess1")?)
        .map_err(|e| e.to_string())?;
    let id2 = storage
        .insert(&create_api_request_event(1000, "sess1")?)
        .map_err(|e| e.to_string())?;
    let id3 = storage
        .insert(&create_api_request_event(2000, "sess1")?)
        .map_err(|e| e.to_string())?;

    // Query after id1 with explicit DESC direction
    let query = EventQuery::new()
        .api_requests_only()
        .with_after_id(id1)
        .with_direction(Order::Desc)
        .with_limit(100);
    let requests = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(requests.len(), 2);
    // Requests should be ordered by ID descending
    assert_eq!(requests[0].id, Some(id3));
    assert_eq!(requests[1].id, Some(id2));
    Ok(())
}

#[test]
fn test_api_request_query_session_and_after_id() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    let id1 = storage
        .insert(&create_api_request_event(1000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(2000, "sess2")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(3000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(4000, "sess2")?)
        .map_err(|e| e.to_string())?;

    // Filter by session and after_id
    let query = EventQuery::new()
        .api_requests_only()
        .with_session(Some("sess1".to_string()))
        .with_after_id(id1)
        .with_limit(100);
    let requests = storage.query(&query).map_err(|e| e.to_string())?;

    assert_eq!(requests.len(), 1);
    assert_eq!(requests[0].session_id, "sess1");
    assert_eq!(requests[0].timestamp, ts(3000)?);
    Ok(())
}

#[test]
fn test_api_request_query_direction_without_after_id() -> Result<(), String> {
    let (_path, storage) = temp_db()?;

    storage
        .insert(&create_api_request_event(1000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(2000, "sess1")?)
        .map_err(|e| e.to_string())?;
    storage
        .insert(&create_api_request_event(3000, "sess1")?)
        .map_err(|e| e.to_string())?;

    // Default order is Desc (newest first by timestamp)
    let query = EventQuery::new().api_requests_only().with_limit(100);
    let requests = storage.query(&query).map_err(|e| e.to_string())?;
    assert_eq!(requests[0].timestamp, ts(3000)?);
    assert_eq!(requests[2].timestamp, ts(1000)?);

    // Explicit Asc order (oldest first by timestamp)
    let query = EventQuery::new()
        .api_requests_only()
        .with_direction(Order::Asc)
        .with_limit(100);
    let requests = storage.query(&query).map_err(|e| e.to_string())?;
    assert_eq!(requests[0].timestamp, ts(1000)?);
    assert_eq!(requests[2].timestamp, ts(3000)?);
    Ok(())
}
