//! LOT A0 (fondation) — tests purs du domaine :
//! - `Agent::with_profile` ne change **que** `profile_id` ;
//! - `LayoutTree::leaf` retrouve/loupe un node (feuille vs split/grid) ;
//! - variante d'event `DomainEvent::AgentProfileChanged`.
//!
//! Réutilise les fixtures/helpers existants (`helpers::node`, `helpers::session`)
//! et colle au style des tests `entities.rs` / `layout.rs` (ARCHITECTURE §15.4 A0).

mod helpers;

use domain::events::DomainEvent;
use domain::ids::{AgentId, ProfileId, SkillId, TemplateId};
use domain::{
    Agent, AgentOrigin, Direction, LayoutNode, LayoutTree, LeafCell, SkillRef, SkillScope,
    SplitContainer, TemplateVersion, WeightedChild,
};
use helpers::{node, session};
use uuid::Uuid;

fn agent_id(n: u128) -> AgentId {
    AgentId::from_uuid(Uuid::from_u128(n))
}

fn profile_id(n: u128) -> ProfileId {
    ProfileId::from_uuid(Uuid::from_u128(n))
}

fn template_id(n: u128) -> TemplateId {
    TemplateId::from_uuid(Uuid::from_u128(n))
}

fn skill_ref(n: u128) -> SkillRef {
    SkillRef::new(SkillId::from_uuid(Uuid::from_u128(n)), SkillScope::Project)
}

// ---------------------------------------------------------------------------
// Agent::with_profile — ne change QUE profile_id
// ---------------------------------------------------------------------------

/// Un agent issu d'un template, synchronisé, avec des skills : cas le plus riche
/// pour prouver que `with_profile` ne touche à rien d'autre que `profile_id`.
fn rich_agent() -> Agent {
    Agent::new(
        agent_id(1),
        "Architect",
        "agents/architect.md",
        profile_id(10),
        AgentOrigin::FromTemplate {
            template_id: template_id(7),
            synced_template_version: TemplateVersion::INITIAL,
        },
        true,
    )
    .expect("valid rich agent")
    .with_skills(vec![skill_ref(100), skill_ref(101)])
}

#[test]
fn with_profile_changes_only_profile_id() {
    let before = rich_agent();
    let new_profile = profile_id(99);
    let after = before.clone().with_profile(new_profile);

    // Le seul champ muté :
    assert_eq!(after.profile_id, new_profile);
    assert_ne!(after.profile_id, before.profile_id);

    // Tous les autres champs sont strictement inchangés :
    assert_eq!(after.id, before.id);
    assert_eq!(after.name, before.name);
    assert_eq!(after.context_path, before.context_path);
    assert_eq!(after.origin, before.origin);
    assert_eq!(after.synchronized, before.synchronized);
    assert_eq!(after.skills, before.skills);
}

#[test]
fn with_profile_preserves_scratch_origin_and_unsynced() {
    let before = Agent::new(
        agent_id(2),
        "Scratchy",
        "agents/scratchy.md",
        profile_id(10),
        AgentOrigin::Scratch,
        false,
    )
    .expect("valid scratch agent");

    let after = before.clone().with_profile(profile_id(11));

    assert_eq!(after.profile_id, profile_id(11));
    assert_eq!(after.origin, AgentOrigin::Scratch);
    assert!(!after.synchronized);
    assert!(after.skills.is_empty());
    // Tout sauf le profil identique → comparer un clone "patché" prouve l'égalité.
    assert_eq!(after, before.with_profile(profile_id(11)));
}

#[test]
fn with_profile_to_same_profile_is_identity() {
    // Idempotence / égalité attendue : re-poser le même profil ⇒ agent inchangé.
    let before = rich_agent();
    let after = before.clone().with_profile(before.profile_id);
    assert_eq!(after, before);
}

#[test]
fn with_profile_is_idempotent_when_repeated() {
    let base = rich_agent();
    let once = base.clone().with_profile(profile_id(42));
    let twice = once.clone().with_profile(profile_id(42));
    assert_eq!(once, twice);
}

// ---------------------------------------------------------------------------
// LayoutTree::leaf — retrouve / loupe (feuille vs split/grid)
// ---------------------------------------------------------------------------

fn leaf_cell(id: u128, sess: Option<u128>) -> LeafCell {
    LeafCell {
        id: node(id),
        session: sess.map(session),
        agent: None,
        conversation_id: None,
        engine_session_id: None,
        agent_was_running: false,
    }
}

/// Arbre contenant **au moins un split** : racine = split (id 9) de deux feuilles
/// (id 1 avec session, id 2 sans).
fn split_tree() -> LayoutTree {
    LayoutTree::new(LayoutNode::Split(SplitContainer {
        id: node(9),
        direction: Direction::Row,
        children: vec![
            WeightedChild {
                node: LayoutNode::Leaf(leaf_cell(1, Some(100))),
                weight: 1.0,
            },
            WeightedChild {
                node: LayoutNode::Leaf(leaf_cell(2, None)),
                weight: 1.0,
            },
        ],
    }))
}

#[test]
fn leaf_finds_existing_leaf_in_split_tree() {
    let tree = split_tree();

    // Feuille 1 : retrouvée, et c'est exactement le LeafCell attendu.
    let expected = leaf_cell(1, Some(100));
    let found = tree.leaf(node(1)).expect("leaf 1 exists");
    assert_eq!(*found, expected);

    // Feuille 2 (sans session) aussi.
    let found2 = tree.leaf(node(2)).expect("leaf 2 exists");
    assert_eq!(*found2, leaf_cell(2, None));
}

#[test]
fn leaf_returns_none_for_absent_node() {
    let tree = split_tree();
    assert!(tree.leaf(node(404)).is_none());
}

#[test]
fn leaf_returns_none_for_split_node_id() {
    // L'id 9 existe mais désigne un split, pas une feuille ⇒ None.
    let tree = split_tree();
    assert!(tree.leaf(node(9)).is_none());
}

#[test]
fn leaf_returns_none_for_grid_node_id() {
    use domain::{GridCell, GridContainer};

    // Grille 1x1 (id 50) contenant une feuille (id 3).
    let grid = LayoutTree::new(LayoutNode::Grid(GridContainer {
        id: node(50),
        col_weights: vec![1.0],
        row_weights: vec![1.0],
        cells: vec![GridCell {
            node: LayoutNode::Leaf(leaf_cell(3, None)),
            row: 0,
            col: 0,
            row_span: 1,
            col_span: 1,
        }],
    }));

    // L'id de la grille n'est pas une feuille.
    assert!(grid.leaf(node(50)).is_none());
    // La feuille imbriquée est bien retrouvée.
    assert_eq!(
        *grid.leaf(node(3)).expect("nested leaf"),
        leaf_cell(3, None)
    );
}

#[test]
fn leaf_finds_single_root_leaf() {
    let tree = LayoutTree::single(leaf_cell(7, Some(70)));
    assert_eq!(
        *tree.leaf(node(7)).expect("root leaf"),
        leaf_cell(7, Some(70))
    );
    assert!(tree.leaf(node(8)).is_none());
}

// ---------------------------------------------------------------------------
// DomainEvent::AgentProfileChanged — construction & champs
// ---------------------------------------------------------------------------

#[test]
fn agent_profile_changed_carries_agent_and_profile() {
    let aid = agent_id(1);
    let pid = profile_id(2);
    let event = DomainEvent::AgentProfileChanged {
        agent_id: aid,
        profile_id: pid,
    };

    match event {
        DomainEvent::AgentProfileChanged {
            agent_id,
            profile_id,
        } => {
            assert_eq!(agent_id, aid);
            assert_eq!(profile_id, pid);
        }
        other => panic!("expected AgentProfileChanged, got {other:?}"),
    }
}

#[test]
fn agent_profile_changed_equality_and_clone() {
    let e1 = DomainEvent::AgentProfileChanged {
        agent_id: agent_id(1),
        profile_id: profile_id(2),
    };
    // Clone == original ; un profil différent rompt l'égalité.
    assert_eq!(e1.clone(), e1);
    assert_ne!(
        e1,
        DomainEvent::AgentProfileChanged {
            agent_id: agent_id(1),
            profile_id: profile_id(3),
        }
    );
}
