//! Integration tests for project CRUD lifecycle.

use dirigent_projects::{
    CreateProjectParams, FileBasedProjectStore, ProjectFilter, ProjectStore, ProjectUpdate,
};
use uuid::Uuid;

async fn make_store() -> FileBasedProjectStore {
    let dir = tempfile::tempdir().unwrap();
    FileBasedProjectStore::new(dir.into_path()).await.unwrap()
}

#[tokio::test]
async fn test_create_and_get_project() {
    let store = make_store().await;
    let owner = Uuid::now_v7();

    let project = store
        .create_project(CreateProjectParams {
            name: "Test Project".to_string(),
            description: "A test".to_string(),
            icon: Some("🚀".to_string()),
            owner,
            tags: vec!["rust".to_string()],
            languages: vec!["Rust".to_string()],
            metadata: serde_json::json!({}),
        })
        .await
        .unwrap();

    assert_eq!(project.name, "Test Project");
    assert_eq!(project.owner, owner);

    let fetched = store.get_project(&project.id).await.unwrap();
    assert_eq!(fetched.id, project.id);
    assert_eq!(fetched.name, "Test Project");
    assert_eq!(fetched.icon, Some("🚀".to_string()));
}

#[tokio::test]
async fn test_list_projects_empty() {
    let store = make_store().await;
    let projects = store.list_projects(ProjectFilter::default()).await.unwrap();
    assert!(projects.is_empty());
}

#[tokio::test]
async fn test_list_projects_with_filter() {
    let store = make_store().await;
    let owner1 = Uuid::now_v7();
    let owner2 = Uuid::now_v7();

    store
        .create_project(CreateProjectParams {
            name: "Alpha".to_string(),
            owner: owner1,
            tags: vec!["web".to_string()],
            ..default_params()
        })
        .await
        .unwrap();

    store
        .create_project(CreateProjectParams {
            name: "Beta".to_string(),
            owner: owner2,
            tags: vec!["cli".to_string()],
            ..default_params()
        })
        .await
        .unwrap();

    // Filter by owner
    let filtered = store
        .list_projects(ProjectFilter {
            owner: Some(owner1),
            ..Default::default()
        })
        .await
        .unwrap();
    assert_eq!(filtered.len(), 1);
    assert_eq!(filtered[0].name, "Alpha");

    // Filter by name
    let filtered = store
        .list_projects(ProjectFilter {
            name_contains: Some("bet".to_string()),
            ..Default::default()
        })
        .await
        .unwrap();
    assert_eq!(filtered.len(), 1);
    assert_eq!(filtered[0].name, "Beta");

    // Filter by tag
    let filtered = store
        .list_projects(ProjectFilter {
            tags: vec!["web".to_string()],
            ..Default::default()
        })
        .await
        .unwrap();
    assert_eq!(filtered.len(), 1);
    assert_eq!(filtered[0].name, "Alpha");

    // No filter returns all, sorted by name
    let all = store.list_projects(ProjectFilter::default()).await.unwrap();
    assert_eq!(all.len(), 2);
    assert_eq!(all[0].name, "Alpha");
    assert_eq!(all[1].name, "Beta");
}

#[tokio::test]
async fn test_update_project() {
    let store = make_store().await;

    let project = store
        .create_project(CreateProjectParams {
            name: "Original".to_string(),
            ..default_params()
        })
        .await
        .unwrap();

    let updated = store
        .update_project(
            &project.id,
            ProjectUpdate {
                name: Some("Renamed".to_string()),
                description: Some("New description".to_string()),
                tags: Some(vec!["new-tag".to_string()]),
                ..Default::default()
            },
        )
        .await
        .unwrap();

    assert_eq!(updated.name, "Renamed");
    assert_eq!(updated.description, "New description");
    assert_eq!(updated.tags, vec!["new-tag"]);
    assert!(updated.updated_at > project.created_at);

    // Verify persistence
    let fetched = store.get_project(&project.id).await.unwrap();
    assert_eq!(fetched.name, "Renamed");
}

#[tokio::test]
async fn test_delete_project() {
    let store = make_store().await;

    let project = store
        .create_project(CreateProjectParams {
            name: "ToDelete".to_string(),
            ..default_params()
        })
        .await
        .unwrap();

    store.delete_project(&project.id).await.unwrap();

    let err = store.get_project(&project.id).await.unwrap_err();
    assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_)));
}

#[tokio::test]
async fn test_get_nonexistent_project() {
    let store = make_store().await;
    let err = store.get_project(&Uuid::now_v7()).await.unwrap_err();
    assert!(matches!(err, dirigent_projects::ProjectError::NotFound(_)));
}

#[tokio::test]
async fn test_create_empty_name_fails() {
    let store = make_store().await;
    let err = store
        .create_project(CreateProjectParams {
            name: "  ".to_string(),
            ..default_params()
        })
        .await
        .unwrap_err();
    assert!(matches!(
        err,
        dirigent_projects::ProjectError::Validation(_)
    ));
}

#[tokio::test]
async fn test_update_empty_name_fails() {
    let store = make_store().await;
    let project = store
        .create_project(CreateProjectParams {
            name: "Valid".to_string(),
            ..default_params()
        })
        .await
        .unwrap();

    let err = store
        .update_project(
            &project.id,
            ProjectUpdate {
                name: Some("".to_string()),
                ..Default::default()
            },
        )
        .await
        .unwrap_err();
    assert!(matches!(
        err,
        dirigent_projects::ProjectError::Validation(_)
    ));
}

fn default_params() -> CreateProjectParams {
    CreateProjectParams {
        name: String::new(),
        description: String::new(),
        icon: None,
        owner: Uuid::now_v7(),
        tags: vec![],
        languages: vec![],
        metadata: serde_json::json!({}),
    }
}
