use std::path::PathBuf;

use domain::{
    IssueActor, ProjectPath, Sprint, SprintId, SprintOrder, SprintStatus, SprintStore,
    SprintStoreError,
};
use infrastructure::FsSprintStore;
use uuid::Uuid;

struct TempDir(PathBuf);

impl TempDir {
    fn new() -> Self {
        let path = std::env::temp_dir().join(format!("idea-sprints-{}", Uuid::new_v4()));
        std::fs::create_dir_all(&path).unwrap();
        Self(path)
    }

    fn root(&self) -> ProjectPath {
        ProjectPath::new(self.0.to_string_lossy().to_string()).unwrap()
    }

    fn child(&self, rel: &str) -> PathBuf {
        self.0.join(rel)
    }
}

impl Drop for TempDir {
    fn drop(&mut self) {
        let _ = std::fs::remove_dir_all(&self.0);
    }
}

fn sid(n: u128) -> SprintId {
    SprintId::from_uuid(Uuid::from_u128(n))
}

fn sprint(id: SprintId, order: u32, name: &str) -> Sprint {
    Sprint::new(
        id,
        SprintOrder::new(order).unwrap(),
        name,
        None,
        IssueActor::User,
        1_000,
    )
    .unwrap()
}

#[tokio::test]
async fn sprint_store_writes_markdown_doc_and_index() {
    let tmp = TempDir::new();
    let root = tmp.root();
    let store = FsSprintStore::new();
    let sprint = sprint(sid(1), 1, "Delivery");

    store.create(&root, &sprint).await.unwrap();

    let sprint_md =
        std::fs::read_to_string(tmp.child(&format!(".ideai/sprints/{}/sprint.md", sprint.id)))
            .unwrap();
    let index = std::fs::read_to_string(tmp.child(".ideai/sprints/index.json")).unwrap();

    assert!(sprint_md.starts_with("---\n"));
    assert!(sprint_md.contains("name: \"Delivery\""));
    assert!(index.contains("\"name\": \"Delivery\""));

    let loaded = store.get(&root, sprint.id).await.unwrap();
    assert_eq!(loaded.name, "Delivery");
    assert_eq!(loaded.order.get(), 1);
    assert_eq!(loaded.status, SprintStatus::Planned);
}

#[tokio::test]
async fn sprint_store_update_checks_expected_version() {
    let tmp = TempDir::new();
    let root = tmp.root();
    let store = FsSprintStore::new();
    let original = sprint(sid(2), 1, "Plan");
    store.create(&root, &original).await.unwrap();
    let updated = original
        .clone()
        .mutate(IssueActor::System, 2_000, |sprint| {
            sprint.name = "Build".to_owned();
        })
        .unwrap();

    let err = store
        .update(&root, &updated, updated.version)
        .await
        .unwrap_err();
    assert!(matches!(err, SprintStoreError::VersionConflict { .. }));

    store
        .update(&root, &updated, original.version)
        .await
        .unwrap();
    let loaded = store.get(&root, updated.id).await.unwrap();
    assert_eq!(loaded.name, "Build");
    assert_eq!(loaded.version.get(), 2);
}

#[tokio::test]
async fn sprint_store_lists_by_order_and_deletes() {
    let tmp = TempDir::new();
    let root = tmp.root();
    let store = FsSprintStore::new();
    let first = sprint(sid(10), 2, "Second");
    let second = sprint(sid(11), 1, "First");
    store.create(&root, &first).await.unwrap();
    store.create(&root, &second).await.unwrap();

    let rows = store.list(&root).await.unwrap();
    assert_eq!(
        rows.iter().map(|row| row.name.as_str()).collect::<Vec<_>>(),
        vec!["First", "Second"]
    );

    store.delete(&root, first.id).await.unwrap();
    assert!(matches!(
        store.get(&root, first.id).await.unwrap_err(),
        SprintStoreError::NotFound
    ));
    let rows = store.list(&root).await.unwrap();
    assert_eq!(rows.len(), 1);
    assert_eq!(rows[0].id, second.id);
}
