//! Integration tests for the Notification repository

use attune_common::{
    models::{enums::NotificationState, notification::Notification, JsonDict},
    repositories::{
        notification::{CreateNotificationInput, NotificationRepository, UpdateNotificationInput},
        Create, Delete, FindById, List, Update,
    },
};
use serde_json::json;
use sqlx::PgPool;
use std::sync::atomic::{AtomicU64, Ordering};

mod helpers;
use helpers::create_test_pool;

static NOTIFICATION_COUNTER: AtomicU64 = AtomicU64::new(0);

/// Test fixture for creating unique notifications
struct NotificationFixture {
    pool: PgPool,
    id_suffix: u64,
}

impl NotificationFixture {
    fn new(pool: PgPool) -> Self {
        let id_suffix = NOTIFICATION_COUNTER.fetch_add(1, Ordering::SeqCst);
        Self { pool, id_suffix }
    }

    fn unique_channel(&self, base: &str) -> String {
        format!("{}_{}", base, self.id_suffix)
    }

    fn unique_entity(&self, base: &str) -> String {
        format!("{}_{}", base, self.id_suffix)
    }

    async fn create_notification(
        &self,
        channel: &str,
        entity_type: &str,
        entity: &str,
        activity: &str,
        state: NotificationState,
        content: Option<JsonDict>,
    ) -> Notification {
        let input = CreateNotificationInput {
            channel: channel.to_string(),
            entity_type: entity_type.to_string(),
            entity: entity.to_string(),
            activity: activity.to_string(),
            state,
            content,
        };

        NotificationRepository::create(&self.pool, input)
            .await
            .expect("Failed to create notification")
    }

    async fn create_default(&self) -> Notification {
        let channel = self.unique_channel("test_channel");
        let entity = self.unique_entity("test_entity");
        self.create_notification(
            &channel,
            "execution",
            &entity,
            "created",
            NotificationState::Created,
            None,
        )
        .await
    }

    async fn create_with_content(&self, content: JsonDict) -> Notification {
        let channel = self.unique_channel("test_channel");
        let entity = self.unique_entity("test_entity");
        self.create_notification(
            &channel,
            "execution",
            &entity,
            "created",
            NotificationState::Created,
            Some(content),
        )
        .await
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_notification_minimal() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel = fixture.unique_channel("test_channel");
    let entity = fixture.unique_entity("entity_123");

    let input = CreateNotificationInput {
        channel: channel.clone(),
        entity_type: "execution".to_string(),
        entity: entity.clone(),
        activity: "created".to_string(),
        state: NotificationState::Created,
        content: None,
    };

    let notification = NotificationRepository::create(&pool, input)
        .await
        .expect("Failed to create notification");

    assert!(notification.id > 0);
    assert_eq!(notification.channel, channel);
    assert_eq!(notification.entity_type, "execution");
    assert_eq!(notification.entity, entity);
    assert_eq!(notification.activity, "created");
    assert_eq!(notification.state, NotificationState::Created);
    assert!(notification.content.is_none());
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_notification_with_content() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel = fixture.unique_channel("test_channel");
    let entity = fixture.unique_entity("entity_456");
    let content = json!({
        "execution_id": 123,
        "status": "running",
        "progress": 50
    });

    let input = CreateNotificationInput {
        channel: channel.clone(),
        entity_type: "execution".to_string(),
        entity: entity.clone(),
        activity: "updated".to_string(),
        state: NotificationState::Queued,
        content: Some(content.clone()),
    };

    let notification = NotificationRepository::create(&pool, input)
        .await
        .expect("Failed to create notification");

    assert!(notification.id > 0);
    assert_eq!(notification.channel, channel);
    assert_eq!(notification.state, NotificationState::Queued);
    assert!(notification.content.is_some());
    assert_eq!(notification.content.unwrap(), content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_create_notification_all_states() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let states = [
        NotificationState::Created,
        NotificationState::Queued,
        NotificationState::Processing,
        NotificationState::Error,
    ];

    for state in states {
        let channel = fixture.unique_channel(&format!("channel_{:?}", state));
        let entity = fixture.unique_entity(&format!("entity_{:?}", state));

        let input = CreateNotificationInput {
            channel,
            entity_type: "test".to_string(),
            entity,
            activity: "test".to_string(),
            state,
            content: None,
        };

        let notification = NotificationRepository::create(&pool, input)
            .await
            .expect("Failed to create notification");

        assert_eq!(notification.state, state);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_notification_by_id() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;

    let found = NotificationRepository::find_by_id(&pool, created.id)
        .await
        .expect("Failed to find notification")
        .expect("Notification not found");

    assert_eq!(found.id, created.id);
    assert_eq!(found.channel, created.channel);
    assert_eq!(found.entity_type, created.entity_type);
    assert_eq!(found.entity, created.entity);
    assert_eq!(found.activity, created.activity);
    assert_eq!(found.state, created.state);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_notification_by_id_not_found() {
    let pool = create_test_pool().await.expect("Failed to create pool");

    let result = NotificationRepository::find_by_id(&pool, 999_999_999)
        .await
        .expect("Query should succeed");

    assert!(result.is_none());
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_notification_state() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;
    assert_eq!(created.state, NotificationState::Created);

    let update_input = UpdateNotificationInput {
        state: Some(NotificationState::Processing),
        content: None,
    };

    let updated = NotificationRepository::update(&pool, created.id, update_input)
        .await
        .expect("Failed to update notification");

    assert_eq!(updated.id, created.id);
    assert_eq!(updated.state, NotificationState::Processing);
    assert_eq!(updated.channel, created.channel);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_notification_content() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;
    assert!(created.content.is_none());

    let new_content = json!({
        "error": "Something went wrong",
        "code": 500
    });

    let update_input = UpdateNotificationInput {
        state: None,
        content: Some(new_content.clone()),
    };

    let updated = NotificationRepository::update(&pool, created.id, update_input)
        .await
        .expect("Failed to update notification");

    assert_eq!(updated.id, created.id);
    assert!(updated.content.is_some());
    assert_eq!(updated.content.unwrap(), new_content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_notification_state_and_content() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;

    let new_content = json!({
        "message": "Processing complete"
    });

    let update_input = UpdateNotificationInput {
        state: Some(NotificationState::Processing),
        content: Some(new_content.clone()),
    };

    let updated = NotificationRepository::update(&pool, created.id, update_input)
        .await
        .expect("Failed to update notification");

    assert_eq!(updated.state, NotificationState::Processing);
    assert_eq!(updated.content.unwrap(), new_content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_notification_no_changes() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;

    let update_input = UpdateNotificationInput {
        state: None,
        content: None,
    };

    let updated = NotificationRepository::update(&pool, created.id, update_input)
        .await
        .expect("Failed to update notification");

    // Should return existing entity unchanged
    assert_eq!(updated.id, created.id);
    assert_eq!(updated.state, created.state);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_notification_timestamps() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;
    let created_timestamp = created.created;
    let original_updated = created.updated;

    // Small delay to ensure timestamp difference
    tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;

    let update_input = UpdateNotificationInput {
        state: Some(NotificationState::Queued),
        content: None,
    };

    let updated = NotificationRepository::update(&pool, created.id, update_input)
        .await
        .expect("Failed to update notification");

    // created should be unchanged
    assert_eq!(updated.created, created_timestamp);
    // updated should be newer
    assert!(updated.updated > original_updated);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_notification() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;

    let deleted = NotificationRepository::delete(&pool, created.id)
        .await
        .expect("Failed to delete notification");

    assert!(deleted);

    let found = NotificationRepository::find_by_id(&pool, created.id)
        .await
        .expect("Query should succeed");

    assert!(found.is_none());
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_delete_notification_not_found() {
    let pool = create_test_pool().await.expect("Failed to create pool");

    let deleted = NotificationRepository::delete(&pool, 999_999_999)
        .await
        .expect("Delete should succeed");

    assert!(!deleted);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_list_notifications() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    // Create multiple notifications
    let n1 = fixture.create_default().await;
    tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
    let n2 = fixture.create_default().await;
    tokio::time::sleep(tokio::time::Duration::from_millis(10)).await;
    let n3 = fixture.create_default().await;

    let notifications = NotificationRepository::list(&pool)
        .await
        .expect("Failed to list notifications");

    // Should contain our created notifications
    let ids: Vec<i64> = notifications.iter().map(|n| n.id).collect();
    assert!(ids.contains(&n1.id));
    assert!(ids.contains(&n2.id));
    assert!(ids.contains(&n3.id));

    // Should be ordered by created DESC (newest first)
    let our_notifications: Vec<&Notification> = notifications
        .iter()
        .filter(|n| [n1.id, n2.id, n3.id].contains(&n.id))
        .collect();

    if our_notifications.len() >= 3 {
        // Find positions of our notifications
        let pos1 = notifications.iter().position(|n| n.id == n1.id).unwrap();
        let pos2 = notifications.iter().position(|n| n.id == n2.id).unwrap();
        let pos3 = notifications.iter().position(|n| n.id == n3.id).unwrap();

        // n3 (newest) should come before n2, which should come before n1 (oldest)
        assert!(pos3 < pos2);
        assert!(pos2 < pos1);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_state() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel1 = fixture.unique_channel("channel_1");
    let entity1 = fixture.unique_entity("entity_1");

    let channel2 = fixture.unique_channel("channel_2");
    let entity2 = fixture.unique_entity("entity_2");

    // Create notifications with different states
    let n1 = fixture
        .create_notification(
            &channel1,
            "execution",
            &entity1,
            "created",
            NotificationState::Queued,
            None,
        )
        .await;

    let n2 = fixture
        .create_notification(
            &channel2,
            "execution",
            &entity2,
            "created",
            NotificationState::Queued,
            None,
        )
        .await;

    let _n3 = fixture
        .create_notification(
            &fixture.unique_channel("channel_3"),
            "execution",
            &fixture.unique_entity("entity_3"),
            "created",
            NotificationState::Processing,
            None,
        )
        .await;

    let queued = NotificationRepository::find_by_state(&pool, NotificationState::Queued)
        .await
        .expect("Failed to find by state");

    let queued_ids: Vec<i64> = queued.iter().map(|n| n.id).collect();
    assert!(queued_ids.contains(&n1.id));
    assert!(queued_ids.contains(&n2.id));

    // All returned notifications should be Queued
    for notification in &queued {
        assert_eq!(notification.state, NotificationState::Queued);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_state_empty() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    // Create notification with Created state
    let _n = fixture.create_default().await;

    // Query for Error state (none should exist for our test data)
    let errors = NotificationRepository::find_by_state(&pool, NotificationState::Error)
        .await
        .expect("Failed to find by state");

    // Should not contain our notification
    // (might contain others from other tests, so just verify it works)
    assert!(errors.iter().all(|n| n.state == NotificationState::Error));
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_channel() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel1 = fixture.unique_channel("channel_alpha");
    let channel2 = fixture.unique_channel("channel_beta");

    // Create notifications on different channels
    let n1 = fixture
        .create_notification(
            &channel1,
            "execution",
            &fixture.unique_entity("entity_1"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    let n2 = fixture
        .create_notification(
            &channel1,
            "execution",
            &fixture.unique_entity("entity_2"),
            "updated",
            NotificationState::Queued,
            None,
        )
        .await;

    let _n3 = fixture
        .create_notification(
            &channel2,
            "execution",
            &fixture.unique_entity("entity_3"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    let channel1_notifications = NotificationRepository::find_by_channel(&pool, &channel1)
        .await
        .expect("Failed to find by channel");

    let channel1_ids: Vec<i64> = channel1_notifications.iter().map(|n| n.id).collect();
    assert!(channel1_ids.contains(&n1.id));
    assert!(channel1_ids.contains(&n2.id));

    // All returned notifications should be on channel1
    for notification in &channel1_notifications {
        assert_eq!(notification.channel, channel1);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_channel_empty() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let nonexistent_channel = fixture.unique_channel("nonexistent_channel_xyz");

    let notifications = NotificationRepository::find_by_channel(&pool, &nonexistent_channel)
        .await
        .expect("Failed to find by channel");

    assert!(notifications.is_empty());
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_with_complex_content() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let complex_content = json!({
        "execution_id": 123,
        "status": "completed",
        "result": {
            "stdout": "Command executed successfully",
            "stderr": "",
            "exit_code": 0
        },
        "metrics": {
            "duration_ms": 1500,
            "memory_mb": 128
        },
        "tags": ["production", "automated"]
    });

    let notification = fixture.create_with_content(complex_content.clone()).await;

    assert!(notification.content.is_some());
    assert_eq!(notification.content.unwrap(), complex_content);

    // Verify it's retrievable
    let found = NotificationRepository::find_by_id(&pool, notification.id)
        .await
        .expect("Failed to find notification")
        .expect("Notification not found");

    assert_eq!(found.content.unwrap(), complex_content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_entity_types() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let entity_types = ["execution", "inquiry", "enforcement", "sensor", "action"];

    for entity_type in entity_types {
        let channel = fixture.unique_channel("test_channel");
        let entity = fixture.unique_entity(&format!("entity_{}", entity_type));

        let notification = fixture
            .create_notification(
                &channel,
                entity_type,
                &entity,
                "created",
                NotificationState::Created,
                None,
            )
            .await;

        assert_eq!(notification.entity_type, entity_type);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_activity_types() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let activities = ["created", "updated", "completed", "failed", "cancelled"];

    for activity in activities {
        let channel = fixture.unique_channel("test_channel");
        let entity = fixture.unique_entity(&format!("entity_{}", activity));

        let notification = fixture
            .create_notification(
                &channel,
                "execution",
                &entity,
                activity,
                NotificationState::Created,
                None,
            )
            .await;

        assert_eq!(notification.activity, activity);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_ordering_by_created() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel = fixture.unique_channel("ordered_channel");

    // Create notifications with slight delays
    let n1 = fixture
        .create_notification(
            &channel,
            "execution",
            &fixture.unique_entity("e1"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

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

    let n2 = fixture
        .create_notification(
            &channel,
            "execution",
            &fixture.unique_entity("e2"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

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

    let n3 = fixture
        .create_notification(
            &channel,
            "execution",
            &fixture.unique_entity("e3"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    // Query by channel (should be ordered DESC by created)
    let notifications = NotificationRepository::find_by_channel(&pool, &channel)
        .await
        .expect("Failed to find by channel");

    let ids: Vec<i64> = notifications.iter().map(|n| n.id).collect();

    // Should be in reverse chronological order
    let pos1 = ids.iter().position(|&id| id == n1.id).unwrap();
    let pos2 = ids.iter().position(|&id| id == n2.id).unwrap();
    let pos3 = ids.iter().position(|&id| id == n3.id).unwrap();

    assert!(pos3 < pos2); // n3 (newest) before n2
    assert!(pos2 < pos1); // n2 before n1 (oldest)
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_timestamps_auto_set() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let before = chrono::Utc::now();
    let notification = fixture.create_default().await;
    let after = chrono::Utc::now();

    assert!(notification.created >= before);
    assert!(notification.created <= after);
    assert!(notification.updated >= before);
    assert!(notification.updated <= after);
    // Initially, created and updated should be very close
    assert!(
        (notification.updated - notification.created)
            .num_milliseconds()
            .abs()
            < 1000
    );
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_multiple_notifications_same_entity() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel = fixture.unique_channel("execution_channel");
    let entity = fixture.unique_entity("execution_123");

    // Create multiple notifications for the same entity with different activities
    let n1 = fixture
        .create_notification(
            &channel,
            "execution",
            &entity,
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    let n2 = fixture
        .create_notification(
            &channel,
            "execution",
            &entity,
            "running",
            NotificationState::Processing,
            None,
        )
        .await;

    let n3 = fixture
        .create_notification(
            &channel,
            "execution",
            &entity,
            "completed",
            NotificationState::Processing,
            None,
        )
        .await;

    // All should exist with same entity but different activities
    assert_eq!(n1.entity, entity);
    assert_eq!(n2.entity, entity);
    assert_eq!(n3.entity, entity);

    assert_eq!(n1.activity, "created");
    assert_eq!(n2.activity, "running");
    assert_eq!(n3.activity, "completed");
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_content_null_vs_empty_json() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    // Notification with no content
    let n1 = fixture.create_default().await;
    assert!(n1.content.is_none());

    // Notification with empty JSON object
    let n2 = fixture.create_with_content(json!({})).await;
    assert!(n2.content.is_some());
    assert_eq!(n2.content.as_ref().unwrap(), &json!({}));

    // They should be different
    assert_ne!(n1.content, n2.content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_update_notification_content_to_null() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    // Create with content
    let notification = fixture.create_with_content(json!({"key": "value"})).await;
    assert!(notification.content.is_some());

    // Update content to explicit null (empty JSON object in this case)
    let update_input = UpdateNotificationInput {
        state: None,
        content: Some(json!(null)),
    };

    let updated = NotificationRepository::update(&pool, notification.id, update_input)
        .await
        .expect("Failed to update notification");

    assert!(updated.content.is_some());
    assert_eq!(updated.content.unwrap(), json!(null));
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_state_transition_workflow() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    // Create notification in Created state
    let notification = fixture.create_default().await;
    assert_eq!(notification.state, NotificationState::Created);

    // Transition to Queued
    let n = NotificationRepository::update(
        &pool,
        notification.id,
        UpdateNotificationInput {
            state: Some(NotificationState::Queued),
            content: None,
        },
    )
    .await
    .expect("Failed to update");
    assert_eq!(n.state, NotificationState::Queued);

    // Transition to Processing
    let n = NotificationRepository::update(
        &pool,
        notification.id,
        UpdateNotificationInput {
            state: Some(NotificationState::Processing),
            content: None,
        },
    )
    .await
    .expect("Failed to update");
    assert_eq!(n.state, NotificationState::Processing);

    // Transition to Error
    let n = NotificationRepository::update(
        &pool,
        notification.id,
        UpdateNotificationInput {
            state: Some(NotificationState::Error),
            content: Some(json!({"error": "Failed to deliver"})),
        },
    )
    .await
    .expect("Failed to update");
    assert_eq!(n.state, NotificationState::Error);
    assert_eq!(n.content.unwrap(), json!({"error": "Failed to deliver"}));
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_list_limit() {
    let pool = create_test_pool().await.expect("Failed to create pool");

    let notifications = NotificationRepository::list(&pool)
        .await
        .expect("Failed to list notifications");

    // List should respect LIMIT 1000
    assert!(notifications.len() <= 1000);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_with_special_characters() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel = fixture.unique_channel("channel_with_special_chars_!@#$%");
    let entity = fixture.unique_entity("entity_with_unicode_🚀");

    let notification = fixture
        .create_notification(
            &channel,
            "execution",
            &entity,
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    assert_eq!(notification.channel, channel);
    assert_eq!(notification.entity, entity);

    // Verify retrieval
    let found = NotificationRepository::find_by_id(&pool, notification.id)
        .await
        .expect("Failed to find notification")
        .expect("Notification not found");

    assert_eq!(found.channel, channel);
    assert_eq!(found.entity, entity);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_with_long_strings() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    // PostgreSQL pg_notify has a 63-character limit on channel names
    // Use reasonable-length channel name
    let channel = format!("channel_{}", fixture.id_suffix);

    // But entity and activity can be very long (TEXT fields)
    let long_entity = format!(
        "entity_{}_with_very_long_id_{}",
        fixture.id_suffix,
        "y".repeat(200)
    );
    let long_activity = format!("activity_with_long_name_{}", "z".repeat(200));

    let notification = fixture
        .create_notification(
            &channel,
            "execution",
            &long_entity,
            &long_activity,
            NotificationState::Created,
            None,
        )
        .await;

    assert_eq!(notification.channel, channel);
    assert_eq!(notification.entity, long_entity);
    assert_eq!(notification.activity, long_activity);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_find_by_state_with_multiple_states() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel = fixture.unique_channel("multi_state_channel");

    // Create notifications with all states
    let _n1 = fixture
        .create_notification(
            &channel,
            "execution",
            &fixture.unique_entity("e1"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    let _n2 = fixture
        .create_notification(
            &channel,
            "execution",
            &fixture.unique_entity("e2"),
            "created",
            NotificationState::Queued,
            None,
        )
        .await;

    let _n3 = fixture
        .create_notification(
            &channel,
            "execution",
            &fixture.unique_entity("e3"),
            "created",
            NotificationState::Processing,
            None,
        )
        .await;

    let _n4 = fixture
        .create_notification(
            &channel,
            "execution",
            &fixture.unique_entity("e4"),
            "created",
            NotificationState::Error,
            None,
        )
        .await;

    // Query each state
    let created = NotificationRepository::find_by_state(&pool, NotificationState::Created)
        .await
        .expect("Failed to find created");
    assert!(created.iter().any(|n| n.channel == channel));

    let queued = NotificationRepository::find_by_state(&pool, NotificationState::Queued)
        .await
        .expect("Failed to find queued");
    assert!(queued.iter().any(|n| n.channel == channel));

    let processing = NotificationRepository::find_by_state(&pool, NotificationState::Processing)
        .await
        .expect("Failed to find processing");
    assert!(processing.iter().any(|n| n.channel == channel));

    let errors = NotificationRepository::find_by_state(&pool, NotificationState::Error)
        .await
        .expect("Failed to find errors");
    assert!(errors.iter().any(|n| n.channel == channel));
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_content_array() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let array_content = json!([
        {"step": 1, "status": "completed"},
        {"step": 2, "status": "running"},
        {"step": 3, "status": "pending"}
    ]);

    let notification = fixture.create_with_content(array_content.clone()).await;

    assert_eq!(notification.content.unwrap(), array_content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_content_string_value() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let string_content = json!("Simple string message");

    let notification = fixture.create_with_content(string_content.clone()).await;

    assert_eq!(notification.content.unwrap(), string_content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_content_number_value() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let number_content = json!(42);

    let notification = fixture.create_with_content(number_content.clone()).await;

    assert_eq!(notification.content.unwrap(), number_content);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_parallel_creation() {
    let pool = create_test_pool().await.expect("Failed to create pool");

    // Create multiple notifications concurrently
    let mut handles = vec![];

    for i in 0..10 {
        let pool_clone = pool.clone();
        let handle = tokio::spawn(async move {
            let fixture = NotificationFixture::new(pool_clone);
            let channel = fixture.unique_channel(&format!("parallel_channel_{}", i));
            let entity = fixture.unique_entity(&format!("parallel_entity_{}", i));

            fixture
                .create_notification(
                    &channel,
                    "execution",
                    &entity,
                    "created",
                    NotificationState::Created,
                    None,
                )
                .await
        });
        handles.push(handle);
    }

    let results = futures::future::join_all(handles).await;

    // All should succeed
    for result in results {
        assert!(result.is_ok());
        let notification = result.unwrap();
        assert!(notification.id > 0);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_channel_case_sensitive() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let channel_lower = fixture.unique_channel("testchannel");
    let channel_upper = format!("{}_UPPER", channel_lower);

    let n1 = fixture
        .create_notification(
            &channel_lower,
            "execution",
            &fixture.unique_entity("e1"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    let n2 = fixture
        .create_notification(
            &channel_upper,
            "execution",
            &fixture.unique_entity("e2"),
            "created",
            NotificationState::Created,
            None,
        )
        .await;

    // Channels should be treated as different
    assert_ne!(n1.channel, n2.channel);

    // Query by each channel
    let lower_results = NotificationRepository::find_by_channel(&pool, &channel_lower)
        .await
        .expect("Failed to find by channel");
    assert!(lower_results.iter().any(|n| n.id == n1.id));
    assert!(!lower_results.iter().any(|n| n.id == n2.id));

    let upper_results = NotificationRepository::find_by_channel(&pool, &channel_upper)
        .await
        .expect("Failed to find by channel");
    assert!(!upper_results.iter().any(|n| n.id == n1.id));
    assert!(upper_results.iter().any(|n| n.id == n2.id));
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_entity_type_variations() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    // Test various entity type names
    let entity_types = [
        "execution",
        "inquiry",
        "enforcement",
        "sensor",
        "action",
        "rule",
        "trigger",
        "custom_type",
        "webhook",
        "timer",
    ];

    for entity_type in entity_types {
        let channel = fixture.unique_channel("test_channel");
        let entity = fixture.unique_entity(&format!("entity_{}", entity_type));

        let notification = fixture
            .create_notification(
                &channel,
                entity_type,
                &entity,
                "created",
                NotificationState::Created,
                None,
            )
            .await;

        assert_eq!(notification.entity_type, entity_type);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_update_same_state() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let notification = fixture.create_default().await;
    let original_state = notification.state;
    let original_updated = notification.updated;

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

    // Update to the same state
    let update_input = UpdateNotificationInput {
        state: Some(original_state),
        content: None,
    };

    let updated = NotificationRepository::update(&pool, notification.id, update_input)
        .await
        .expect("Failed to update notification");

    assert_eq!(updated.state, original_state);
    // Updated timestamp should still change
    assert!(updated.updated > original_updated);
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_multiple_updates() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let notification = fixture.create_default().await;

    // Perform multiple updates
    for i in 0..5 {
        let content = json!({"update_count": i});
        let update_input = UpdateNotificationInput {
            state: None,
            content: Some(content.clone()),
        };

        let updated = NotificationRepository::update(&pool, notification.id, update_input)
            .await
            .expect("Failed to update notification");

        assert_eq!(updated.content.unwrap(), content);
    }
}

#[tokio::test]
#[ignore = "integration test — requires database"]
async fn test_notification_get_by_id_alias() {
    let pool = create_test_pool().await.expect("Failed to create pool");
    let fixture = NotificationFixture::new(pool.clone());

    let created = fixture.create_default().await;

    // Test using get_by_id (which should call find_by_id internally and unwrap)
    let found = NotificationRepository::get_by_id(&pool, created.id)
        .await
        .expect("Failed to get notification");

    assert_eq!(found.id, created.id);
    assert_eq!(found.channel, created.channel);
}
