//! Sync Event Log Integration Test
//!
//! Tests the persistent event logging system for sync operations.
//! Verifies that critical events are captured and queryable.

mod helpers;

use helpers::{MockTransport, TestDataDir};
use sd_core::{
	infra::{
		db::entities,
		sync::{EventCategory, EventSeverity, NetworkTransport, SyncEventQuery, SyncEventType},
	},
	library::Library,
	service::{sync::state::DeviceSyncState, Service},
	Core,
};
use sea_orm::{ConnectionTrait, DatabaseBackend, Statement};
use std::{path::PathBuf, sync::Arc};
use tokio::{fs, time::Duration};
use uuid::Uuid;

/// Test harness for event log testing
struct EventLogTestHarness {
	_test_data_alice: TestDataDir,
	core_alice: Core,
	library_alice: Arc<Library>,
	device_alice_id: Uuid,
	transport_alice: Arc<MockTransport>,
	snapshot_dir: PathBuf,
}

impl EventLogTestHarness {
	async fn new(test_name: &str) -> anyhow::Result<Self> {
		// Use TestDataDir helper for proper cross-platform directory management
		let test_data_alice = TestDataDir::new(format!("event_log_{}", test_name))?;
		let temp_dir_alice = test_data_alice.core_data_path();

		// Create snapshot directory
		let timestamp = chrono::Utc::now().format("%Y%m%d_%H%M%S");
		let snapshot_dir = test_data_alice
			.path()
			.join("snapshots")
			.join(timestamp.to_string());
		fs::create_dir_all(&snapshot_dir).await?;

		// Initialize tracing
		let _ = tracing_subscriber::fmt()
			.with_test_writer()
			.with_env_filter("sd_core::service::sync=debug,sd_core::infra::sync::event_log=trace")
			.try_init();

		tracing::info!(
			test_data_dir = %test_data_alice.path().display(),
			snapshot_dir = %snapshot_dir.display(),
			"Event log test initialized"
		);

		// Initialize core
		let core_alice = Core::new(temp_dir_alice.clone())
			.await
			.map_err(|e| anyhow::anyhow!("{}", e))?;
		let device_alice_id = core_alice.device.device_id()?;

		// Create library without auto-sync
		let library_alice = core_alice
			.libraries
			.create_library_no_sync("Event Log Test", None, core_alice.context.clone())
			.await?;

		// Create mock transport (single device, no actual sync)
		let transport_alice = MockTransport::new_single(device_alice_id);

		// Initialize sync service
		library_alice
			.init_sync_service(
				device_alice_id,
				transport_alice.clone() as Arc<dyn NetworkTransport>,
			)
			.await?;

		// Start sync service
		library_alice.sync_service().unwrap().start().await?;

		tracing::info!(
			device_id = %device_alice_id,
			"Test harness initialized"
		);

		Ok(Self {
			_test_data_alice: test_data_alice,
			core_alice,
			library_alice,
			device_alice_id,
			transport_alice,
			snapshot_dir,
		})
	}

	/// Query events directly from sync.db
	async fn query_events_raw(&self) -> anyhow::Result<Vec<(String, String, String)>> {
		let sync_service = self.library_alice.sync_service().unwrap();
		let event_logger = sync_service.event_logger();

		let stmt = Statement::from_string(
			DatabaseBackend::Sqlite,
			"SELECT event_type, summary, correlation_id FROM sync_event_log ORDER BY timestamp"
				.to_string(),
		);

		let rows = event_logger.conn().query_all(stmt).await?;

		let mut events = Vec::new();
		for row in rows {
			let event_type: String = row.try_get("", "event_type")?;
			let summary: String = row.try_get("", "summary")?;
			let correlation_id: Option<String> = row.try_get("", "correlation_id").ok();
			events.push((event_type, summary, correlation_id.unwrap_or_default()));
		}

		Ok(events)
	}

	/// Query events using the query API
	async fn query_events_api(
		&self,
		query: SyncEventQuery,
	) -> anyhow::Result<Vec<sd_core::infra::sync::SyncEventLog>> {
		let sync_service = self.library_alice.sync_service().unwrap();
		sync_service.event_logger().query(query).await
	}
}

#[tokio::test]
async fn test_state_transition_events_logged() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("state_transitions").await?;

	// Trigger state transition
	tracing::info!("Setting state to Backfilling");
	harness
		.library_alice
		.sync_service()
		.unwrap()
		.peer_sync()
		.set_state_for_test(DeviceSyncState::Backfilling {
			peer: Uuid::new_v4(),
			progress: 0,
		})
		.await;

	// Give event logger time to write
	tokio::time::sleep(Duration::from_millis(100)).await;

	// Query events
	let events = harness.query_events_raw().await?;

	tracing::info!(event_count = events.len(), "Events logged");

	// Should have at least one state transition event
	let state_transitions: Vec<_> = events
		.iter()
		.filter(|(t, _, _)| t == "state_transition")
		.collect();

	assert!(
		!state_transitions.is_empty(),
		"Expected state transition events, got: {:?}",
		events
	);

	// Verify summary contains state names
	let has_backfilling = state_transitions
		.iter()
		.any(|(_, summary, _)| summary.contains("Backfilling"));

	assert!(
		has_backfilling,
		"Expected Backfilling in summary, summaries: {:?}",
		state_transitions
	);

	Ok(())
}

#[tokio::test]
async fn test_backfill_session_correlation() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("backfill_correlation").await?;

	// Note: We can't easily trigger a real backfill without a second device,
	// but we can query for correlation_id structure

	// Query events using API with correlation filter
	let library_id = harness.library_alice.id();
	let query = SyncEventQuery::new(library_id);

	let events = harness.query_events_api(query).await?;

	tracing::info!(event_count = events.len(), "Events retrieved via query API");

	// Verify query API works (even if no events yet)
	assert!(
		events.len() >= 0,
		"Query API should return results (even if empty)"
	);

	Ok(())
}

#[tokio::test]
async fn test_event_query_filtering() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("query_filtering").await?;

	let library_id = harness.library_alice.id();

	// Trigger multiple state transitions
	let states = vec![
		DeviceSyncState::Backfilling {
			peer: Uuid::new_v4(),
			progress: 0,
		},
		DeviceSyncState::CatchingUp { buffered_count: 10 },
		DeviceSyncState::Ready,
	];

	for state in states {
		harness
			.library_alice
			.sync_service()
			.unwrap()
			.peer_sync()
			.set_state_for_test(state)
			.await;
		tokio::time::sleep(Duration::from_millis(50)).await;
	}

	// Give events time to flush
	tokio::time::sleep(Duration::from_millis(200)).await;

	// Test filtering by event type
	let query =
		SyncEventQuery::new(library_id).with_event_types(vec![SyncEventType::StateTransition]);

	let events = harness.query_events_api(query).await?;

	tracing::info!(filtered_count = events.len(), "Filtered events");

	// Should have multiple state transitions
	assert!(
		events.len() >= 2,
		"Expected multiple state transitions, got {}",
		events.len()
	);

	// All should be state transitions
	for event in &events {
		assert_eq!(event.event_type, SyncEventType::StateTransition);
	}

	// Test filtering by category
	let query_category =
		SyncEventQuery::new(library_id).with_categories(vec![EventCategory::Lifecycle]);

	let lifecycle_events = harness.query_events_api(query_category).await?;

	assert!(
		lifecycle_events.len() >= events.len(),
		"Lifecycle category should include all state transitions"
	);

	Ok(())
}

#[tokio::test]
async fn test_event_retention_cleanup() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("retention_cleanup").await?;

	let sync_service = harness.library_alice.sync_service().unwrap();
	let event_logger = sync_service.event_logger();

	// Create a test event with old timestamp
	use chrono::Utc;
	let old_timestamp = Utc::now() - chrono::Duration::days(10);

	// Insert old event directly
	let stmt = Statement::from_sql_and_values(
		DatabaseBackend::Sqlite,
		r#"
		INSERT INTO sync_event_log (
			timestamp, device_id, event_type, category, severity, summary
		) VALUES (?, ?, ?, ?, ?, ?)
		"#,
		vec![
			old_timestamp.to_rfc3339().into(),
			harness.device_alice_id.to_string().into(),
			"state_transition".into(),
			"lifecycle".into(),
			"info".into(),
			"Old test event".into(),
		],
	);

	event_logger.conn().execute(stmt).await?;

	// Verify event exists
	let before_count = harness.query_events_raw().await?.len();
	assert!(before_count > 0, "Test event should be inserted");

	// Run cleanup (7-day retention)
	let cutoff = Utc::now() - chrono::Duration::days(7);
	let deleted = event_logger.cleanup_old_events(cutoff).await?;

	tracing::info!(deleted, "Events deleted by cleanup");

	// Old event should be deleted
	assert!(deleted > 0, "Should have deleted old event");

	// Query again
	let after_count = harness.query_events_raw().await?.len();
	assert!(
		after_count < before_count,
		"Event count should decrease after cleanup"
	);

	Ok(())
}

#[tokio::test]
async fn test_batch_aggregation() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("batch_aggregation").await?;

	let sync_service = harness.library_alice.sync_service().unwrap();
	let batch_aggregator = sync_service.batch_aggregator();

	// Add records to aggregator
	batch_aggregator
		.add_records("entry".to_string(), 100, None)
		.await;
	batch_aggregator
		.add_records("tag".to_string(), 50, None)
		.await;
	batch_aggregator
		.add_records("location".to_string(), 10, None)
		.await;

	// Manually flush (normally happens on 30s timer)
	batch_aggregator.flush_all().await;

	// Give time for async write
	tokio::time::sleep(Duration::from_millis(200)).await;

	// Query batch ingestion events
	let library_id = harness.library_alice.id();
	let query =
		SyncEventQuery::new(library_id).with_event_types(vec![SyncEventType::BatchIngestion]);

	let events = harness.query_events_api(query).await?;

	tracing::info!(batch_events = events.len(), "Batch ingestion events logged");

	// Should have one batch event aggregating all the adds
	assert!(
		events.len() >= 1,
		"Expected at least one batch ingestion event"
	);

	// Verify batch contains record counts
	let batch_event = &events[0];
	assert!(
		batch_event.record_count.is_some(),
		"Batch event should have record count"
	);
	assert!(
		batch_event.record_count.unwrap() >= 160,
		"Batch should aggregate 100+50+10=160 records"
	);

	// Verify model types are tracked
	assert!(
		batch_event.model_types.is_some(),
		"Batch should track model types"
	);

	let model_types = batch_event.model_types.as_ref().unwrap();
	assert!(
		model_types.contains(&"entry".to_string()),
		"Should include entry model type"
	);

	Ok(())
}

#[tokio::test]
async fn test_buffer_overflow_logging() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("buffer_overflow").await?;

	let sync_service = harness.library_alice.sync_service().unwrap();
	let peer_sync = sync_service.peer_sync();

	// Set to backfilling state (enables buffer)
	peer_sync
		.set_state_for_test(DeviceSyncState::Backfilling {
			peer: Uuid::new_v4(),
			progress: 0,
		})
		.await;

	tokio::time::sleep(Duration::from_millis(100)).await;

	// Fill buffer beyond capacity by directly accessing buffer
	// (In real scenario, this happens when receiving too many updates during backfill)
	// For testing, we'll simulate by tracking drops manually

	// Transition to Ready (this checks for dropped count)
	peer_sync.set_state_for_test(DeviceSyncState::Ready).await;

	tokio::time::sleep(Duration::from_millis(200)).await;

	// Query for sync errors
	let library_id = harness.library_alice.id();
	let query = SyncEventQuery::new(library_id)
		.with_event_types(vec![SyncEventType::SyncError])
		.with_severities(vec![EventSeverity::Error, EventSeverity::Warning]);

	let error_events = harness.query_events_api(query).await?;

	tracing::info!(error_count = error_events.len(), "Error events logged");

	// Note: Buffer overflow only logs if drops actually occurred
	// This test verifies the infrastructure exists, even if no drops happened
	assert!(error_events.len() >= 0, "Error event query should work");

	Ok(())
}

#[tokio::test]
async fn test_event_log_persistence() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("persistence").await?;

	// Trigger some events
	harness
		.library_alice
		.sync_service()
		.unwrap()
		.peer_sync()
		.set_state_for_test(DeviceSyncState::Ready)
		.await;

	tokio::time::sleep(Duration::from_millis(200)).await;

	// Query events
	let library_id = harness.library_alice.id();
	let query = SyncEventQuery::new(library_id);
	let events_before = harness.query_events_api(query.clone()).await?;

	assert!(
		events_before.len() >= 1,
		"Should have logged state transition"
	);

	// Verify events are in sync.db (copy to snapshot)
	let sync_db_path = harness.library_alice.path().join("sync.db");
	assert!(sync_db_path.exists(), "sync.db should exist");

	// Copy to snapshot
	let snapshot_path = harness.snapshot_dir.join("sync.db");
	fs::copy(&sync_db_path, &snapshot_path).await?;

	tracing::info!(
		snapshot = %snapshot_path.display(),
		event_count = events_before.len(),
		"Events persisted to sync.db"
	);

	// Restart sync service (simulates app restart)
	drop(harness.library_alice.sync_service());

	// Re-initialize sync service
	harness
		.library_alice
		.init_sync_service(
			harness.device_alice_id,
			harness.transport_alice.clone() as Arc<dyn NetworkTransport>,
		)
		.await?;

	harness
		.library_alice
		.sync_service()
		.unwrap()
		.start()
		.await?;

	tokio::time::sleep(Duration::from_millis(100)).await;

	// Query events again (should still exist after restart)
	let events_after = harness.query_events_api(query).await?;

	assert_eq!(
		events_after.len(),
		events_before.len(),
		"Events should persist across sync service restart"
	);

	Ok(())
}

#[tokio::test]
async fn test_correlation_id_tracking() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("correlation_tracking").await?;

	// Manually insert events with same correlation ID (simulating backfill session)
	let sync_service = harness.library_alice.sync_service().unwrap();
	let event_logger = sync_service.event_logger();
	let session_id = Uuid::new_v4();

	use sd_core::infra::sync::SyncEventLog;

	// Event 1: Session started
	let event1 = SyncEventLog::new(
		harness.device_alice_id,
		SyncEventType::BackfillSessionStarted,
		"Test backfill started",
	)
	.with_correlation_id(session_id);

	event_logger.log(event1).await?;

	// Event 2: Batch ingestion (same session)
	let event2 = SyncEventLog::new(
		harness.device_alice_id,
		SyncEventType::BatchIngestion,
		"Test batch ingested",
	)
	.with_correlation_id(session_id)
	.with_record_count(1000);

	event_logger.log(event2).await?;

	// Event 3: Session completed
	let event3 = SyncEventLog::new(
		harness.device_alice_id,
		SyncEventType::BackfillSessionCompleted,
		"Test backfill completed",
	)
	.with_correlation_id(session_id)
	.with_duration_ms(5000);

	event_logger.log(event3).await?;

	tokio::time::sleep(Duration::from_millis(100)).await;

	// Query by correlation ID
	let library_id = harness.library_alice.id();
	let query = SyncEventQuery::new(library_id).with_correlation_id(session_id);

	let session_events = harness.query_events_api(query).await?;

	tracing::info!(
		session_event_count = session_events.len(),
		session_id = %session_id,
		"Events with correlation ID"
	);

	// Should have exactly 3 events with this correlation ID
	assert_eq!(
		session_events.len(),
		3,
		"Expected 3 events with correlation_id"
	);

	// Verify they're in order
	assert_eq!(
		session_events[0].event_type,
		SyncEventType::BackfillSessionStarted
	);
	assert_eq!(session_events[1].event_type, SyncEventType::BatchIngestion);
	assert_eq!(
		session_events[2].event_type,
		SyncEventType::BackfillSessionCompleted
	);

	// Verify all have same correlation ID
	for event in &session_events {
		assert_eq!(event.correlation_id, Some(session_id));
	}

	Ok(())
}

#[tokio::test]
async fn test_query_pagination() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("pagination").await?;

	let sync_service = harness.library_alice.sync_service().unwrap();
	let event_logger = sync_service.event_logger();

	// Insert 10 events
	for i in 0..10 {
		use sd_core::infra::sync::SyncEventLog;
		let event = SyncEventLog::new(
			harness.device_alice_id,
			SyncEventType::StateTransition,
			format!("Test event {}", i),
		);
		event_logger.log(event).await?;
	}

	tokio::time::sleep(Duration::from_millis(200)).await;

	let library_id = harness.library_alice.id();

	// Query first page
	let query_page1 = SyncEventQuery::new(library_id).with_limit(5).with_offset(0);

	let page1 = harness.query_events_api(query_page1).await?;

	// Query second page
	let query_page2 = SyncEventQuery::new(library_id).with_limit(5).with_offset(5);

	let page2 = harness.query_events_api(query_page2).await?;

	tracing::info!(
		page1_count = page1.len(),
		page2_count = page2.len(),
		"Paginated query results"
	);

	assert!(page1.len() <= 5, "Page 1 should respect limit");
	assert!(page2.len() <= 5, "Page 2 should respect limit");

	// Pages should not overlap
	if !page1.is_empty() && !page2.is_empty() {
		assert_ne!(
			page1[0].id, page2[0].id,
			"Pages should contain different events"
		);
	}

	Ok(())
}

#[tokio::test]
async fn test_severity_filtering() -> anyhow::Result<()> {
	let harness = EventLogTestHarness::new("severity_filtering").await?;

	let sync_service = harness.library_alice.sync_service().unwrap();
	let event_logger = sync_service.event_logger();

	// Insert events with different severities
	use sd_core::infra::sync::SyncEventLog;

	let error_event = SyncEventLog::new(
		harness.device_alice_id,
		SyncEventType::SyncError,
		"Test error",
	);
	event_logger.log(error_event).await?;

	let info_event = SyncEventLog::new(
		harness.device_alice_id,
		SyncEventType::StateTransition,
		"Test info",
	);
	event_logger.log(info_event).await?;

	tokio::time::sleep(Duration::from_millis(100)).await;

	let library_id = harness.library_alice.id();

	// Query only errors
	let query_errors = SyncEventQuery::new(library_id).with_severities(vec![EventSeverity::Error]);

	let errors = harness.query_events_api(query_errors).await?;

	tracing::info!(error_count = errors.len(), "Error events");

	assert!(errors.len() >= 1, "Should have at least one error event");

	// All should be Error severity
	for event in &errors {
		assert_eq!(event.severity, EventSeverity::Error);
	}

	Ok(())
}
