// Copyright (c) 2026 - present Nicholas D. Crosbie
// SPDX-License-Identifier: MIT

//! Integration tests for hindsight-mcp
//!
//! These tests verify full database round-trips and cross-crate data flow.

use chrono::{TimeZone, Utc};
use hindsight_copilot::session::{ChatMessage, ChatSession, MessageRole};
use hindsight_git::commit::Commit;
use hindsight_mcp::db::Database;
use hindsight_mcp::ingest::{IngestOptions, Ingestor, ProgressEvent};
use hindsight_tests::result::{TestOutcome, TestResult};

/// Helper to generate a UUID-like string for testing
fn test_uuid(suffix: &str) -> String {
    format!("00000000-0000-0000-0000-{:0>12}", suffix)
}

/// Helper to generate an ISO 8601 timestamp string
fn iso_timestamp() -> String {
    Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string()
}

#[test]
fn test_commit_to_json_for_database() {
    let commit = Commit {
        sha: "1945ab9c752534e733c38ba0109dc3b741f0a6eb".to_string(),
        message: "feat: add integration tests\n\nDetailed description.".to_string(),
        author: "Test Author".to_string(),
        author_email: "test@example.com".to_string(),
        timestamp: Utc.with_ymd_and_hms(2026, 1, 17, 2, 33, 6).unwrap(),
        parents: vec!["c460aeb7fb2d109c17e43de0ce681faec0b7374d".to_string()],
    };

    // Serialize for database storage
    let json = serde_json::to_string(&commit).expect("Failed to serialize commit");

    // Verify it's valid JSON
    let _parsed: serde_json::Value = serde_json::from_str(&json).expect("Should be valid JSON");

    // Check that parents are stored as JSON array (for parents_json column)
    let parents_json = serde_json::to_string(&commit.parents).expect("Failed to serialize parents");
    let parents_parsed: Vec<String> = serde_json::from_str(&parents_json).expect("Should parse");
    assert_eq!(parents_parsed.len(), 1);

    println!("Commit JSON: {}", json);
    println!("Parents JSON: {}", parents_json);
}

#[test]
fn test_test_result_to_json_for_database() {
    let result = TestResult {
        name: "hindsight_git::commit::tests::test_is_valid_sha".to_string(),
        outcome: TestOutcome::Passed,
        duration_ms: 42,
        timestamp: Utc.with_ymd_and_hms(2026, 1, 17, 2, 33, 6).unwrap(),
        output: Some("test output here".to_string()),
    };

    // Serialize for database storage
    let json = serde_json::to_string(&result).expect("Failed to serialize test result");

    // The output should be stored as JSON (for output_json column)
    let output_json = result
        .output
        .as_ref()
        .map(|o| serde_json::json!({"stdout": o, "stderr": null}))
        .map(|v| serde_json::to_string(&v).expect("Failed to serialize output"));

    println!("TestResult JSON: {}", json);
    if let Some(oj) = &output_json {
        println!("Output JSON: {}", oj);
    }
}

#[test]
fn test_chat_session_to_json_for_database() {
    let timestamp = Utc.with_ymd_and_hms(2026, 1, 17, 2, 33, 6).unwrap();

    let mut session = ChatSession::new(test_uuid("session1"), test_uuid("workspace1"), timestamp);

    session.add_message(ChatMessage::user("Hello".to_string(), timestamp));
    session.add_message(ChatMessage::assistant("Hi there!".to_string(), timestamp));

    // Serialize for database storage
    let json = serde_json::to_string_pretty(&session).expect("Failed to serialize session");

    // Metadata for the metadata_json column
    let metadata = serde_json::json!({
        "version": 3,
        "responder": "GitHub Copilot",
        "message_count": session.message_count()
    });
    let metadata_json = serde_json::to_string(&metadata).expect("Failed to serialize metadata");

    println!("Session JSON: {}", json);
    println!("Metadata JSON: {}", metadata_json);
}

#[test]
fn test_cross_crate_type_compatibility() {
    // Verify that types from different crates can be used together
    let commit_timestamp = Utc.with_ymd_and_hms(2026, 1, 17, 2, 33, 6).unwrap();

    let commit = Commit {
        sha: "1945ab9c752534e733c38ba0109dc3b741f0a6eb".to_string(),
        message: "test: add tests".to_string(),
        author: "Test".to_string(),
        author_email: "test@test.com".to_string(),
        timestamp: commit_timestamp,
        parents: vec![],
    };

    let test_result = TestResult {
        name: "test_something".to_string(),
        outcome: TestOutcome::Passed,
        duration_ms: 10,
        timestamp: commit_timestamp, // Same timestamp type
        output: None,
    };

    let session = ChatSession::new(
        "session".to_string(),
        "workspace".to_string(),
        commit_timestamp, // Same timestamp type
    );

    // All types use chrono::DateTime<Utc> for timestamps
    assert_eq!(commit.timestamp, test_result.timestamp);
    assert_eq!(commit.timestamp, session.created_at);

    // All types are serializable
    let _ = serde_json::to_string(&commit).expect("commit serializable");
    let _ = serde_json::to_string(&test_result).expect("test_result serializable");
    let _ = serde_json::to_string(&session).expect("session serializable");
}

#[test]
fn test_timeline_data_structure() {
    // Simulate a timeline combining all data sources
    #[derive(Debug, serde::Serialize)]
    struct TimelineEvent {
        event_type: String,
        timestamp: String,
        content: String,
        reference_id: String,
    }

    let timestamp = Utc.with_ymd_and_hms(2026, 1, 17, 2, 33, 6).unwrap();
    let iso = timestamp.format("%Y-%m-%dT%H:%M:%SZ").to_string();

    let events = vec![
        TimelineEvent {
            event_type: "commit".to_string(),
            timestamp: iso.clone(),
            content: "feat: add new feature".to_string(),
            reference_id: "1945ab9c".to_string(),
        },
        TimelineEvent {
            event_type: "test_run".to_string(),
            timestamp: iso.clone(),
            content: "Tests: 102 passed, 0 failed".to_string(),
            reference_id: test_uuid("run1"),
        },
        TimelineEvent {
            event_type: "copilot".to_string(),
            timestamp: iso.clone(),
            content: "How do I implement this?".to_string(),
            reference_id: test_uuid("session1"),
        },
    ];

    let json = serde_json::to_string_pretty(&events).expect("Failed to serialize timeline");

    // Verify structure
    let parsed: Vec<serde_json::Value> = serde_json::from_str(&json).expect("Should parse");
    assert_eq!(parsed.len(), 3);

    println!("Timeline JSON:\n{}", json);
}

#[test]
fn test_uuid_and_timestamp_format() {
    // Verify UUID format
    let uuid = test_uuid("abc123");
    assert_eq!(uuid.len(), 36, "UUID should be 36 characters");
    assert!(uuid.contains('-'), "UUID should contain dashes");

    // Verify ISO 8601 timestamp format
    let ts = iso_timestamp();
    assert!(ts.contains('T'), "Timestamp should contain T separator");
    assert!(ts.ends_with('Z'), "Timestamp should end with Z for UTC");

    // Verify chrono serialization matches expected format
    let dt = Utc.with_ymd_and_hms(2026, 1, 17, 2, 33, 6).unwrap();
    let json = serde_json::to_string(&dt).expect("Failed to serialize datetime");

    // chrono uses RFC 3339 which is compatible with ISO 8601
    assert!(json.contains("2026-01-17"));
    assert!(json.contains("02:33:06"));

    println!("UUID: {}", uuid);
    println!("ISO timestamp: {}", ts);
    println!("Chrono JSON: {}", json);
}

#[test]
fn test_json_column_structures() {
    // Define JSON structures for database columns

    // parents_json for commits table
    let parents: Vec<String> = vec![
        "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa".to_string(),
        "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb".to_string(),
    ];
    let parents_json = serde_json::to_string(&parents).expect("parents");

    // diff_json for commits table
    let diff = serde_json::json!({
        "files_changed": 3,
        "additions": 100,
        "deletions": 20,
        "files": [
            {"path": "src/lib.rs", "additions": 50, "deletions": 10},
            {"path": "src/main.rs", "additions": 30, "deletions": 5},
            {"path": "README.md", "additions": 20, "deletions": 5}
        ]
    });
    let diff_json = serde_json::to_string(&diff).expect("diff");

    // metadata_json for test_runs table
    let test_metadata = serde_json::json!({
        "nextest_version": "0.9",
        "profile": "default",
        "target_triple": "aarch64-apple-darwin"
    });
    let test_metadata_json = serde_json::to_string(&test_metadata).expect("test_metadata");

    // output_json for test_results table
    let output = serde_json::json!({
        "stdout": "test output...",
        "stderr": null,
        "status_code": 0
    });
    let output_json = serde_json::to_string(&output).expect("output");

    // variables_json for copilot_messages table
    let variables = serde_json::json!({
        "files": [
            {"name": "lib.rs", "uri": "file:///workspace/src/lib.rs"},
            {"name": "main.rs", "uri": "file:///workspace/src/main.rs"}
        ],
        "selections": []
    });
    let variables_json = serde_json::to_string(&variables).expect("variables");

    // Verify all are valid JSON
    let _: Vec<String> = serde_json::from_str(&parents_json).expect("parse parents");
    let _: serde_json::Value = serde_json::from_str(&diff_json).expect("parse diff");
    let _: serde_json::Value = serde_json::from_str(&test_metadata_json).expect("parse metadata");
    let _: serde_json::Value = serde_json::from_str(&output_json).expect("parse output");
    let _: serde_json::Value = serde_json::from_str(&variables_json).expect("parse variables");

    println!("All JSON column structures are valid");
}

#[test]
fn test_message_role_as_database_enum() {
    // Verify MessageRole serializes to lowercase strings suitable for database
    let roles = [
        MessageRole::User,
        MessageRole::Assistant,
        MessageRole::System,
    ];

    for role in &roles {
        let json = serde_json::to_string(role).expect("serialize");
        let value: String = serde_json::from_str(&json).expect("parse");

        // Should be lowercase
        assert_eq!(value, value.to_lowercase());

        // Should be a simple string (no complex structure)
        assert!(!value.contains('{'));
    }
}

#[test]
fn test_test_outcome_as_database_enum() {
    // Verify TestOutcome serializes to lowercase strings suitable for database
    let outcomes = [
        TestOutcome::Passed,
        TestOutcome::Failed,
        TestOutcome::Ignored,
        TestOutcome::TimedOut,
    ];

    for outcome in &outcomes {
        let json = serde_json::to_string(outcome).expect("serialize");
        let value: String = serde_json::from_str(&json).expect("parse");

        // Should be lowercase
        assert_eq!(value, value.to_lowercase());

        // Should be a simple string
        assert!(!value.contains('{'));
    }
}

// ============================================================================
// Ingestor Integration Tests
// ============================================================================

#[test]
fn test_ingestor_new_with_in_memory_database() {
    // Create an in-memory database and initialize it
    let db = Database::in_memory().expect("Failed to create in-memory database");
    db.initialize().expect("Failed to initialize database");

    // Create the ingestor
    let ingestor = Ingestor::new(db);

    // Verify we can access the database
    assert!(ingestor.database().is_initialized());
}

#[test]
fn test_ingestor_with_progress_callback() {
    use std::sync::{Arc, Mutex};

    let db = Database::in_memory().expect("Failed to create in-memory database");
    db.initialize().expect("Failed to initialize database");

    // Collect progress events
    let events: Arc<Mutex<Vec<String>>> = Arc::new(Mutex::new(Vec::new()));
    let events_clone = Arc::clone(&events);

    let callback: Box<dyn Fn(&ProgressEvent) + Send + Sync> = Box::new(move |event| {
        let msg = match event {
            ProgressEvent::Started { source, .. } => format!("started:{}", source),
            ProgressEvent::Progress {
                source, processed, ..
            } => {
                format!("progress:{}:{}", source, processed)
            }
            ProgressEvent::Warning { source, message } => format!("warning:{}:{}", source, message),
            ProgressEvent::Completed { source, .. } => format!("completed:{}", source),
        };
        events_clone.lock().unwrap().push(msg);
    });

    let ingestor = Ingestor::new(db).with_progress(callback);

    // Progress callback is set
    drop(ingestor);

    // Events may or may not have been triggered depending on actions
    // This test just verifies the callback can be set without panicking
}

#[test]
fn test_ingest_git_on_current_repository() {
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create in-memory database");
    db.initialize().expect("Failed to initialize database");

    let mut ingestor = Ingestor::new(db);

    // Try to ingest from the current repository (this project)
    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    // Check if we're in a git repository
    if repo_path.join(".git").exists() {
        let options = IngestOptions::full().with_limit(10);

        let result = ingestor.ingest_git(repo_path, &options);

        // Should succeed
        assert!(result.is_ok(), "ingest_git failed: {:?}", result.err());

        let stats = result.unwrap();

        // Should have ingested some commits
        assert!(
            stats.commits_inserted > 0,
            "Expected at least one commit to be ingested"
        );

        println!("Ingested {} commits", stats.commits_inserted);
    } else {
        println!("Skipping test: not in a git repository");
    }
}

#[test]
fn test_ingest_tests_with_sample_output() {
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create in-memory database");
    db.initialize().expect("Failed to initialize database");

    let mut ingestor = Ingestor::new(db);

    // Sample nextest output (minimal valid JSON)
    let nextest_output = r#"{
        "message-version": "0.1",
        "started": "2026-01-17T02:33:06Z",
        "finished": "2026-01-17T02:33:10Z",
        "duration_ms": 4000,
        "passed": 3,
        "failed": 0,
        "ignored": 1,
        "total": 4,
        "results": [
            {"name": "tests::test_one", "outcome": "passed", "duration_ms": 10},
            {"name": "tests::test_two", "outcome": "passed", "duration_ms": 20},
            {"name": "tests::test_three", "outcome": "passed", "duration_ms": 15},
            {"name": "tests::test_ignored", "outcome": "ignored", "duration_ms": 0}
        ]
    }"#;

    let workspace_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    let result = ingestor.ingest_tests(workspace_path, nextest_output, None);

    // Note: This may fail if the JSON doesn't match nextest's exact format
    // The test documents the expected behavior
    match result {
        Ok(stats) => {
            assert_eq!(stats.test_runs_inserted, 1);
            assert!(stats.test_results_inserted > 0);
            println!(
                "Ingested test run with {} results",
                stats.test_results_inserted
            );
        }
        Err(e) => {
            println!("Test ingestion not supported with this format: {}", e);
        }
    }
}

#[test]
fn test_ingest_options_default_values() {
    let options = IngestOptions::default();

    // Verify default values - defaults are all false/None
    assert_eq!(options.commit_limit, None);
    assert!(!options.include_diffs);
    assert!(!options.incremental);
}

#[test]
fn test_ingest_options_builder_pattern() {
    let options = IngestOptions::full().with_limit(50);

    assert_eq!(options.commit_limit, Some(50));
    assert!(options.include_diffs);
    // full() sets incremental to false
    assert!(!options.incremental);
}

#[test]
fn test_incremental_git_ingestion() {
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create in-memory database");
    db.initialize().expect("Failed to initialize database");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // First ingestion - limit to 5 commits with incremental mode
    let options = IngestOptions::incremental().with_limit(5);

    let stats1 = ingestor
        .ingest_git(repo_path, &options)
        .expect("First ingestion failed");

    // Verify first ingestion worked
    assert!(
        stats1.commits_inserted > 0,
        "Expected at least one commit on first ingestion"
    );

    // Second ingestion with same options - should skip already ingested
    let stats2 = ingestor
        .ingest_git(repo_path, &options)
        .expect("Second ingestion failed");

    // The second ingestion should have skipped at least some commits
    // (the ones already in the database)
    // Either commits were skipped, or nothing new was inserted (because we hit the same limit)
    let total_second = stats2.commits_inserted + stats2.commits_skipped;
    assert!(
        stats2.commits_skipped > 0 || total_second <= stats1.commits_inserted,
        "Expected either skipped commits or reduced new commits on re-ingestion. \
         First: {} inserted, Second: {} inserted, {} skipped",
        stats1.commits_inserted,
        stats2.commits_inserted,
        stats2.commits_skipped
    );

    println!(
        "First: {} inserted, Second: {} inserted, {} skipped",
        stats1.commits_inserted, stats2.commits_inserted, stats2.commits_skipped
    );
}

// ============================================================================
// End-to-End Validation Tests (Phase 5)
// ============================================================================

/// Test full pipeline: ingest real commits and query them via timeline view
#[test]
fn test_e2e_ingest_and_query_timeline() {
    use hindsight_mcp::queries::get_timeline;
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Ingest 20 commits with diffs
    let options = IngestOptions::full().with_limit(20);
    let stats = ingestor
        .ingest_git(repo_path, &options)
        .expect("Git ingestion failed");

    assert!(stats.commits_inserted > 0, "Expected at least one commit");

    // Query the timeline
    let db = ingestor.database();
    let timeline = get_timeline(db.connection(), 50, None).expect("Timeline query failed");

    assert!(
        !timeline.is_empty(),
        "Expected timeline to have events after ingestion"
    );

    // Verify timeline events have commit type
    let commit_events: Vec<_> = timeline
        .iter()
        .filter(|e| e.event_type == "commit")
        .collect();
    assert!(
        !commit_events.is_empty(),
        "Expected commit events in timeline"
    );

    println!(
        "Timeline: {} total events, {} commit events",
        timeline.len(),
        commit_events.len()
    );
}

/// Test FTS5 full-text search on ingested commits
#[test]
fn test_e2e_fts5_commit_search() {
    use hindsight_mcp::queries::search_commits;
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Ingest commits
    let options = IngestOptions::full().with_limit(50);
    let stats = ingestor
        .ingest_git(repo_path, &options)
        .expect("Git ingestion failed");

    assert!(stats.commits_inserted > 0, "Expected at least one commit");

    // Search for common commit message patterns
    let db = ingestor.database();

    // Search for "feat" - common in conventional commits
    let results = search_commits(db.connection(), "feat", 10).expect("Search failed");

    // We expect at least some results since this repo uses conventional commits
    println!(
        "Search for 'feat': {} results from {} commits",
        results.len(),
        stats.commits_inserted
    );

    // Search for implementation-related terms
    let results = search_commits(db.connection(), "implement", 10).expect("Search failed");
    println!("Search for 'implement': {} results", results.len());
}

/// Test cross-table joins via commit_with_tests query
#[test]
fn test_e2e_cross_table_joins() {
    use hindsight_mcp::db::{CommitRecord, TestResultRecord, TestRunRecord};
    use hindsight_mcp::queries::get_commit_with_tests;

    let mut db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    // Create a workspace
    let workspace_id = db
        .get_or_create_workspace("test-workspace", "/test/path")
        .expect("Failed to create workspace");

    // Insert a commit
    let commit = CommitRecord::new(
        workspace_id.clone(),
        "abc123def456".to_string(),
        "Test Author".to_string(),
        Some("test@example.com".to_string()),
        "feat: test commit for cross-table join".to_string(),
        Utc::now(),
    );
    db.insert_commit(&commit).expect("Failed to insert commit");

    // Insert a test run linked to the commit
    let run = TestRunRecord::new(workspace_id.clone())
        .with_commit("abc123def456")
        .finished(5, 1, 0);
    let run_id = db.insert_test_run(&run).expect("Failed to insert test run");

    // Insert test results
    let results = vec![
        TestResultRecord::new(
            run_id.clone(),
            "test_suite".to_string(),
            "test_one".to_string(),
            "passed".to_string(),
            Some(10),
        ),
        TestResultRecord::new(
            run_id.clone(),
            "test_suite".to_string(),
            "test_two".to_string(),
            "failed".to_string(),
            Some(50),
        ),
    ];
    db.insert_test_results_batch(&results)
        .expect("Failed to insert results");

    // Query commit with associated tests
    let result = get_commit_with_tests(db.connection(), "abc123def456");

    match result {
        Ok(Some(commit_with_tests)) => {
            assert_eq!(commit_with_tests.sha, "abc123def456");
            assert!(
                !commit_with_tests.test_runs.is_empty(),
                "Expected tests linked to commit"
            );
            println!(
                "Cross-table join: commit {} with {} test runs",
                commit_with_tests.sha,
                commit_with_tests.test_runs.len()
            );
        }
        Ok(None) => {
            println!("Cross-table query: commit not found (expected)");
        }
        Err(e) => {
            println!("Cross-table query error: {:?}", e);
        }
    }
}

/// Test activity summary aggregation
#[test]
fn test_e2e_activity_summary() {
    use hindsight_mcp::db::{CommitRecord, CopilotMessageRecord, CopilotSessionRecord};
    use hindsight_mcp::queries::get_activity_summary;

    let mut db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    // Create a workspace
    let workspace_id = db
        .get_or_create_workspace("summary-workspace", "/summary/path")
        .expect("Failed to create workspace");

    // Insert multiple commits
    for i in 0..5 {
        let commit = CommitRecord::new(
            workspace_id.clone(),
            format!("commit{:03}abc123", i),
            "Developer".to_string(),
            Some("dev@example.com".to_string()),
            format!("feat: feature number {}", i),
            Utc::now(),
        );
        db.insert_commit(&commit).expect("Failed to insert commit");
    }

    // Insert a Copilot session with messages
    let vscode_session_id = test_uuid("summary-session");
    let session = CopilotSessionRecord::new(workspace_id.clone(), vscode_session_id);
    db.insert_copilot_session(&session)
        .expect("Failed to insert session");

    let messages = vec![
        CopilotMessageRecord::new(
            session.id.clone(),
            "user".to_string(),
            "How do I write tests?".to_string(),
            Utc::now(),
        ),
        CopilotMessageRecord::new(
            session.id.clone(),
            "assistant".to_string(),
            "Here's how to write tests in Rust...".to_string(),
            Utc::now(),
        ),
    ];
    db.insert_copilot_messages_batch(&messages)
        .expect("Failed to insert messages");

    // Query activity summary
    let summary = get_activity_summary(db.connection(), 7).expect("Activity summary failed");

    println!(
        "Activity summary: {} commits, {} sessions, {} test runs",
        summary.commits, summary.copilot_sessions, summary.test_runs
    );

    assert!(
        summary.commits >= 5,
        "Expected at least 5 commits in summary"
    );
}

/// Performance baseline test - measures ingestion time for 100 commits
#[test]
fn test_e2e_performance_baseline_ingestion() {
    use std::path::Path;
    use std::time::Instant;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Measure ingestion time for 100 commits with diffs
    let options = IngestOptions::full().with_limit(100);

    let start = Instant::now();
    let stats = ingestor
        .ingest_git(repo_path, &options)
        .expect("Git ingestion failed");
    let ingestion_duration = start.elapsed();

    println!(
        "PERFORMANCE BASELINE - Git Ingestion:\n\
         - Commits ingested: {}\n\
         - Time: {:?}\n\
         - Rate: {:.2} commits/sec",
        stats.commits_inserted,
        ingestion_duration,
        stats.commits_inserted as f64 / ingestion_duration.as_secs_f64()
    );

    // Assert reasonable performance (should be under 10 seconds for 100 commits)
    assert!(
        ingestion_duration.as_secs() < 10,
        "Ingestion took too long: {:?}",
        ingestion_duration
    );
}

/// Performance baseline test - measures query times
#[test]
fn test_e2e_performance_baseline_queries() {
    use hindsight_mcp::queries::{get_activity_summary, get_timeline, search_commits};
    use std::path::Path;
    use std::time::Instant;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Ingest data first
    let options = IngestOptions::full().with_limit(100);
    let stats = ingestor
        .ingest_git(repo_path, &options)
        .expect("Git ingestion failed");

    let db = ingestor.database();
    let conn = db.connection();

    // Measure timeline query
    let start = Instant::now();
    let _timeline = get_timeline(conn, 50, None).expect("Timeline query failed");
    let timeline_duration = start.elapsed();

    // Measure FTS5 search
    let start = Instant::now();
    let _search_results = search_commits(conn, "feat", 20).expect("Search failed");
    let search_duration = start.elapsed();

    // Measure activity summary
    let start = Instant::now();
    let _summary = get_activity_summary(conn, 30).expect("Summary failed");
    let summary_duration = start.elapsed();

    println!(
        "PERFORMANCE BASELINE - Queries ({} commits):\n\
         - Timeline (50 events): {:?}\n\
         - FTS5 search: {:?}\n\
         - Activity summary (30 days): {:?}",
        stats.commits_inserted, timeline_duration, search_duration, summary_duration
    );

    // Assert queries complete in reasonable time (under 100ms each)
    assert!(
        timeline_duration.as_millis() < 100,
        "Timeline query too slow: {:?}",
        timeline_duration
    );
    assert!(
        search_duration.as_millis() < 100,
        "Search query too slow: {:?}",
        search_duration
    );
    assert!(
        summary_duration.as_millis() < 100,
        "Summary query too slow: {:?}",
        summary_duration
    );
}

/// Test full pipeline with all data sources
#[test]
fn test_e2e_full_pipeline_validation() {
    use hindsight_mcp::queries::{get_timeline, search_all};
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Ingest git commits
    let git_options = IngestOptions::full().with_limit(50);
    let git_stats = ingestor
        .ingest_git(repo_path, &git_options)
        .expect("Git ingestion failed");

    // Try to ingest Copilot sessions (may not find any)
    let copilot_stats = ingestor.ingest_copilot(repo_path);
    let copilot_sessions = match copilot_stats {
        Ok(stats) => stats.sessions_inserted,
        Err(_) => 0, // Expected if no Copilot sessions available
    };

    // Validate data was ingested
    let db = ingestor.database();

    // Check timeline has entries
    let timeline = get_timeline(db.connection(), 100, None).expect("Timeline failed");

    // Check search works
    let search = search_all(db.connection(), "test", 10).expect("Search failed");

    println!(
        "FULL PIPELINE VALIDATION:\n\
         - Git commits ingested: {}\n\
         - Copilot sessions: {}\n\
         - Timeline events: {}\n\
         - Search results for 'test': {}",
        git_stats.commits_inserted,
        copilot_sessions,
        timeline.len(),
        search.len()
    );

    // Assertions
    assert!(
        git_stats.commits_inserted > 0,
        "Expected git commits to be ingested"
    );
    assert!(!timeline.is_empty(), "Expected timeline to have events");
}

// ============================================================================
// Handler Integration Tests (Phase 2)
// ============================================================================

#[test]
fn test_handler_timeline_with_ingested_data() {
    use hindsight_mcp::handlers::handle_timeline;
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Ingest some commits
    let options = IngestOptions::full().with_limit(10);
    ingestor
        .ingest_git(repo_path, &options)
        .expect("Git ingestion failed");

    // Call the handler
    let db = ingestor.database();
    let args = serde_json::json!({ "limit": 5 });
    let args_map: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).expect("parse args");

    let result = handle_timeline(db, Some(args_map), None);

    assert!(
        result.is_ok(),
        "Timeline handler failed: {:?}",
        result.err()
    );
    let events = result.unwrap();
    assert!(!events.is_empty(), "Expected timeline events");
    assert!(events.len() <= 5, "Expected at most 5 events");

    println!("Handler returned {} timeline events", events.len());
}

#[test]
fn test_handler_search_with_ingested_data() {
    use hindsight_mcp::handlers::handle_search;
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Ingest commits
    let options = IngestOptions::full().with_limit(50);
    ingestor
        .ingest_git(repo_path, &options)
        .expect("Git ingestion failed");

    // Call the search handler
    let db = ingestor.database();
    let args = serde_json::json!({
        "query": "feat",
        "source": "commits",
        "limit": 10
    });
    let args_map: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).expect("parse args");

    let result = handle_search(db, Some(args_map));

    assert!(result.is_ok(), "Search handler failed: {:?}", result.err());
    let results = result.unwrap();

    println!(
        "Handler returned {} search results for 'feat'",
        results.len()
    );
}

#[test]
fn test_handler_activity_summary() {
    use hindsight_mcp::handlers::handle_activity_summary;
    use std::path::Path;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let mut ingestor = Ingestor::new(db);

    let repo_path = Path::new(env!("CARGO_MANIFEST_DIR"))
        .parent()
        .unwrap()
        .parent()
        .unwrap();

    if !repo_path.join(".git").exists() {
        println!("Skipping test: not in a git repository");
        return;
    }

    // Ingest commits
    let options = IngestOptions::full().with_limit(20);
    ingestor
        .ingest_git(repo_path, &options)
        .expect("Git ingestion failed");

    // Call the handler with default args
    let db = ingestor.database();
    let result = handle_activity_summary(db, None);

    assert!(
        result.is_ok(),
        "Activity summary handler failed: {:?}",
        result.err()
    );
    let summary = result.unwrap();

    assert!(summary.commits > 0, "Expected commits in summary");
    assert_eq!(summary.days, 7, "Expected default 7 days");

    println!(
        "Handler returned activity summary: {} commits, {} test runs",
        summary.commits, summary.test_runs
    );
}

#[test]
fn test_handler_commit_details_not_found() {
    use hindsight_mcp::handlers::{HandlerError, handle_commit_details};

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let args = serde_json::json!({ "sha": "nonexistent123" });
    let args_map: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).expect("parse args");

    let result = handle_commit_details(&db, Some(args_map));

    assert!(result.is_err());
    match result {
        Err(HandlerError::NotFound(msg)) => {
            assert!(msg.contains("nonexistent123"));
        }
        Err(e) => panic!("Expected NotFound error, got: {:?}", e),
        Ok(_) => panic!("Expected error for nonexistent commit"),
    }
}

#[test]
fn test_handler_failing_tests_empty() {
    use hindsight_mcp::handlers::handle_failing_tests;

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let result = handle_failing_tests(&db, None, None);

    assert!(
        result.is_ok(),
        "Failing tests handler failed: {:?}",
        result.err()
    );
    let tests = result.unwrap();

    // Empty database should return empty list
    assert!(tests.is_empty(), "Expected no failing tests in empty db");
}

#[test]
fn test_handler_search_empty_query_error() {
    use hindsight_mcp::handlers::{HandlerError, handle_search};

    let db = Database::in_memory().expect("Failed to create database");
    db.initialize().expect("Failed to initialize");

    let args = serde_json::json!({ "query": "" });
    let args_map: serde_json::Map<String, serde_json::Value> =
        serde_json::from_value(args).expect("parse args");

    let result = handle_search(&db, Some(args_map));

    assert!(result.is_err());
    match result {
        Err(HandlerError::InvalidInput(msg)) => {
            assert!(msg.contains("empty"));
        }
        Err(e) => panic!("Expected InvalidInput error, got: {:?}", e),
        Ok(_) => panic!("Expected error for empty query"),
    }
}

// ============================================================================
// Configuration and Lifecycle Tests (Phase 3)
// ============================================================================

#[test]
fn test_config_default_values() {
    use hindsight_mcp::config::Config;

    let config = Config::default();

    assert!(config.database.is_none());
    assert!(config.workspace.is_none());
    assert!(!config.verbose);
    assert!(!config.quiet);
    assert!(!config.skip_init);
}

#[test]
fn test_config_database_path_default() {
    use hindsight_mcp::config::Config;

    let config = Config::default();
    let path = config.database_path();

    // Should contain "hindsight" in the path
    assert!(
        path.to_string_lossy().contains("hindsight"),
        "Database path should contain 'hindsight'"
    );
}

#[test]
fn test_config_database_path_custom() {
    use hindsight_mcp::config::Config;
    use std::path::PathBuf;

    let custom = PathBuf::from("/custom/database.db");
    let config = Config {
        database: Some(custom.clone()),
        ..Default::default()
    };

    assert_eq!(config.database_path(), custom);
}

#[test]
fn test_config_workspace_detection() {
    use hindsight_mcp::config::Config;

    let config = Config::default();
    let workspace = config.workspace_path();

    // Should default to current directory
    assert!(workspace.is_some(), "Should detect workspace from cwd");
}

#[test]
fn test_config_log_levels() {
    use hindsight_mcp::config::Config;

    // Default: INFO
    let config = Config::default();
    assert_eq!(config.log_level(), tracing::Level::INFO);

    // Verbose: DEBUG
    let config = Config {
        verbose: true,
        ..Default::default()
    };
    assert_eq!(config.log_level(), tracing::Level::DEBUG);

    // Quiet: WARN
    let config = Config {
        quiet: true,
        ..Default::default()
    };
    assert_eq!(config.log_level(), tracing::Level::WARN);
}

#[test]
fn test_config_validate_valid_workspace() {
    use hindsight_mcp::config::Config;
    use std::path::PathBuf;

    let config = Config {
        workspace: Some(PathBuf::from("/tmp")),
        ..Default::default()
    };

    // /tmp exists on most systems
    let result = config.validate();
    assert!(result.is_ok(), "Validation should pass for /tmp");
}

#[test]
fn test_config_validate_invalid_workspace() {
    use hindsight_mcp::config::{Config, ConfigError};
    use std::path::PathBuf;

    let config = Config {
        workspace: Some(PathBuf::from("/nonexistent/path/12345")),
        ..Default::default()
    };

    let result = config.validate();
    assert!(matches!(result, Err(ConfigError::WorkspaceNotFound(_))));
}

#[test]
fn test_database_lifecycle_create_and_initialize() {
    // Test that database can be created and initialized in one flow
    let db = Database::in_memory().expect("Failed to create database");

    assert!(
        !db.is_initialized(),
        "New database should not be initialized"
    );

    db.initialize().expect("Failed to initialize");

    assert!(
        db.is_initialized(),
        "Database should be initialized after init"
    );

    // Verify schema version
    let version = db.schema_version().expect("Failed to get version");
    assert!(version >= 1, "Schema version should be at least 1");
}

#[test]
fn test_database_lifecycle_double_initialize() {
    // Test that calling initialize twice doesn't cause issues
    let db = Database::in_memory().expect("Failed to create database");

    db.initialize().expect("First init should succeed");
    db.initialize().expect("Second init should be idempotent");

    assert!(db.is_initialized());
}

#[test]
fn test_database_file_persistence() {
    use std::fs;

    let temp_dir = std::env::temp_dir().join("hindsight_test_persistence");
    let _ = fs::create_dir_all(&temp_dir);
    let db_path = temp_dir.join("test.db");

    // Clean up from previous runs
    let _ = fs::remove_file(&db_path);

    // Create and initialize
    {
        let db = Database::open(&db_path).expect("Failed to create database");
        db.initialize().expect("Failed to initialize");

        // Insert a commit to verify persistence
        let workspace = hindsight_mcp::db::WorkspaceRecord::new(
            "test-workspace".to_string(),
            "/test/path".to_string(),
        );
        db.insert_workspace(&workspace)
            .expect("Failed to insert workspace");
    }

    // Reopen and verify
    {
        let db = Database::open(&db_path).expect("Failed to reopen database");
        assert!(db.is_initialized(), "Database should still be initialized");

        let count = db.count("workspaces").expect("Failed to count");
        assert_eq!(count, 1, "Workspace should persist");
    }

    // Cleanup
    let _ = fs::remove_file(&db_path);
    let _ = fs::remove_dir(&temp_dir);
}

#[test]
fn test_server_creation_with_workspace() {
    use hindsight_mcp::server::HindsightServer;
    use std::path::PathBuf;

    let db = Database::in_memory().expect("Failed to create database");
    let workspace = PathBuf::from("/test/workspace");

    let server = HindsightServer::new(db, Some(workspace.clone()));

    assert_eq!(server.workspace(), Some(&workspace));
}

#[test]
fn test_server_creation_with_db_path() {
    use hindsight_mcp::server::HindsightServer;
    use std::path::PathBuf;

    let db = Database::in_memory().expect("Failed to create database");
    let db_path = PathBuf::from("/test/database.db");

    let _server = HindsightServer::new(db, None).with_db_path(db_path);

    // Server should be created successfully with db_path set
    // (We can't easily test the internal state, but it shouldn't panic)
}
