//! L10 tests for [`MoveTabToNewWindow`] with a fake [`ProjectStore`]: the tab is
//! detached and the workspace is persisted (load returns the new state).

use std::sync::{Arc, Mutex};

use async_trait::async_trait;
use domain::ids::{ProjectId, TabId, WindowId};
use domain::layout::{LayoutNode, LayoutTree, LeafCell, Tab, Window, Workspace};
use domain::ports::{IdGenerator, ProjectStore, StoreError};
use domain::project::Project;
use domain::NodeId;
use uuid::Uuid;

use application::{MoveTabToNewWindow, MoveTabToNewWindowInput};

/// A `ProjectStore` fake that only implements the workspace persistence the use
/// case needs (the project methods are never called here).
#[derive(Clone)]
struct FakeStore(Arc<Mutex<Workspace>>);
#[async_trait]
impl ProjectStore for FakeStore {
    async fn list_projects(&self) -> Result<Vec<Project>, StoreError> {
        unreachable!()
    }
    async fn load_project(&self, _id: ProjectId) -> Result<Project, StoreError> {
        unreachable!()
    }
    async fn save_project(&self, _p: &Project) -> Result<(), StoreError> {
        unreachable!()
    }
    async fn save_workspace(&self, ws: &Workspace) -> Result<(), StoreError> {
        *self.0.lock().unwrap() = ws.clone();
        Ok(())
    }
    async fn load_workspace(&self) -> Result<Workspace, StoreError> {
        Ok(self.0.lock().unwrap().clone())
    }
}

struct SeqIds(Mutex<u128>);
impl IdGenerator for SeqIds {
    fn new_uuid(&self) -> Uuid {
        let mut n = self.0.lock().unwrap();
        let id = Uuid::from_u128(*n);
        *n += 1;
        id
    }
}

fn tid(n: u128) -> TabId {
    TabId::from_uuid(Uuid::from_u128(n))
}
fn wid(n: u128) -> WindowId {
    WindowId::from_uuid(Uuid::from_u128(n))
}
fn tab(n: u128) -> Tab {
    Tab {
        id: tid(n),
        project_id: ProjectId::from_uuid(Uuid::from_u128(1000 + n)),
        layout: LayoutTree::new(LayoutNode::Leaf(LeafCell {
            id: NodeId::from_uuid(Uuid::from_u128(900 + n)),
            session: None,
            agent: None,
            conversation_id: None,
            engine_session_id: None,
            agent_was_running: false,
        })),
    }
}

fn seeded() -> FakeStore {
    let ws = Workspace {
        windows: vec![Window::new(wid(1), vec![tab(1), tab(2)], tid(1)).unwrap()],
    };
    FakeStore(Arc::new(Mutex::new(ws)))
}

#[tokio::test]
async fn detaches_tab_and_persists_workspace() {
    let store = seeded();
    // The id generator's first uuid (from_u128(7)) becomes the new window id.
    let ids = Arc::new(SeqIds(Mutex::new(7)));
    let uc = MoveTabToNewWindow::new(Arc::new(store.clone()), ids);

    let out = uc
        .execute(MoveTabToNewWindowInput { tab_id: tid(1) })
        .await
        .unwrap();

    assert_eq!(out.new_window_id, WindowId::from_uuid(Uuid::from_u128(7)));
    assert_eq!(out.workspace.windows.len(), 2);

    // Persisted: reloading the store yields the detached layout.
    let reloaded = store.load_workspace().await.unwrap();
    assert_eq!(reloaded, out.workspace);
    let detached = reloaded
        .windows
        .iter()
        .find(|w| w.id == out.new_window_id)
        .unwrap();
    assert_eq!(detached.tabs.len(), 1);
    assert_eq!(detached.tabs[0].id, tid(1));
}

#[tokio::test]
async fn unknown_tab_is_not_found() {
    let store = seeded();
    let uc = MoveTabToNewWindow::new(Arc::new(store), Arc::new(SeqIds(Mutex::new(7))));
    let err = uc
        .execute(MoveTabToNewWindowInput { tab_id: tid(404) })
        .await
        .unwrap_err();
    assert_eq!(err.code(), "NOT_FOUND", "got {err:?}");
}
