// Created: 2026-04-07 by Constructor Tech
use super::*;
use crate::config::TenantConfig;
use tenant_resolver_sdk::TenantStatus;
use uuid::Uuid;

// Helper to create a test tenant config
fn tenant(id: &str, name: &str, status: TenantStatus) -> TenantConfig {
    TenantConfig {
        id: Uuid::parse_str(id).unwrap(),
        name: name.to_owned(),
        status,
        tenant_type: None,
        parent_id: None,
        self_managed: false,
    }
}

fn tenant_with_parent(id: &str, name: &str, parent: &str) -> TenantConfig {
    TenantConfig {
        id: Uuid::parse_str(id).unwrap(),
        name: name.to_owned(),
        status: TenantStatus::Active,
        tenant_type: None,
        parent_id: Some(Uuid::parse_str(parent).unwrap()),
        self_managed: false,
    }
}

fn tenant_barrier(id: &str, name: &str, parent: &str) -> TenantConfig {
    TenantConfig {
        id: Uuid::parse_str(id).unwrap(),
        name: name.to_owned(),
        status: TenantStatus::Active,
        tenant_type: None,
        parent_id: Some(Uuid::parse_str(parent).unwrap()),
        self_managed: true,
    }
}

// Test UUIDs
const TENANT_A: &str = "11111111-1111-1111-1111-111111111111";
const TENANT_B: &str = "22222222-2222-2222-2222-222222222222";
const TENANT_C: &str = "33333333-3333-3333-3333-333333333333";
const TENANT_D: &str = "44444444-4444-4444-4444-444444444444";

// ==================== from_config tests ====================

#[test]
fn from_config_empty_rejects() {
    // Zero roots violates the single-root invariant.
    let cfg = StaticTrPluginConfig::default();
    assert!(Service::from_config(&cfg).is_err());
}

#[test]
fn from_config_multiple_roots_rejects() {
    // Two tenants with no parent violates the single-root invariant.
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Tenant A", TenantStatus::Active),
            tenant(TENANT_B, "Tenant B", TenantStatus::Suspended),
        ],
        ..Default::default()
    };
    assert!(Service::from_config(&cfg).is_err());
}

#[test]
fn from_config_single_root_with_child() {
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Tenant A", TenantStatus::Active),
            tenant_with_parent(TENANT_B, "Tenant B", TENANT_A),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    assert_eq!(service.tenants.len(), 2);

    let a = service
        .tenants
        .get(&TenantId(Uuid::parse_str(TENANT_A).unwrap()))
        .unwrap();
    assert_eq!(a.name, "Tenant A");
    assert_eq!(a.status, TenantStatus::Active);
    assert!(a.parent_id.is_none());
    assert!(!a.self_managed);
    assert_eq!(service.root.id, a.id);
}

#[test]
fn from_config_with_hierarchy() {
    // A -> B -> C (linear hierarchy)
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_with_parent(TENANT_B, "Child", TENANT_A),
            tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    assert_eq!(service.tenants.len(), 3);

    // Check children index
    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    let a_children = service.children.get(&a_id).unwrap();
    assert_eq!(a_children.len(), 1);
    assert!(a_children.contains(&b_id));

    let b_children = service.children.get(&b_id).unwrap();
    assert_eq!(b_children.len(), 1);
    assert!(b_children.contains(&c_id));

    // C has no children
    assert!(!service.children.contains_key(&c_id));
}

// ==================== collect_ancestors tests ====================

#[test]
fn collect_ancestors_root_tenant() {
    let cfg = StaticTrPluginConfig {
        tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");
    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());

    let ancestors = service.collect_ancestors(a_id, BarrierMode::Respect);
    assert!(ancestors.is_empty());
}

#[test]
fn collect_ancestors_linear_hierarchy() {
    // A -> B -> C
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_with_parent(TENANT_B, "Child", TENANT_A),
            tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    // Ancestors of C should be [B, A]
    let ancestors = service.collect_ancestors(c_id, BarrierMode::Respect);
    assert_eq!(ancestors.len(), 2);
    assert_eq!(ancestors[0].id, b_id);
    assert_eq!(ancestors[1].id, a_id);

    // Ancestors of B should be [A]
    let ancestors = service.collect_ancestors(b_id, BarrierMode::Respect);
    assert_eq!(ancestors.len(), 1);
    assert_eq!(ancestors[0].id, a_id);
}

#[test]
fn collect_ancestors_with_barrier() {
    // A -> B (barrier) -> C
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_barrier(TENANT_B, "Barrier", TENANT_A),
            tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    // With BarrierMode::Respect, ancestors of C stop at B
    let ancestors = service.collect_ancestors(c_id, BarrierMode::Respect);
    assert_eq!(ancestors.len(), 1);
    assert_eq!(ancestors[0].id, b_id);

    // With BarrierMode::Ignore, ancestors of C include both B and A
    let ancestors = service.collect_ancestors(c_id, BarrierMode::Ignore);
    assert_eq!(ancestors.len(), 2);
    assert_eq!(ancestors[0].id, b_id);
    assert_eq!(ancestors[1].id, a_id);
}

#[test]
fn collect_ancestors_starting_tenant_is_barrier() {
    // A -> B (barrier)
    // get_ancestors(B) should return empty because B is a barrier
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_barrier(TENANT_B, "Barrier", TENANT_A),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());

    // With BarrierMode::Respect, B cannot see its parent chain
    let ancestors = service.collect_ancestors(b_id, BarrierMode::Respect);
    assert!(ancestors.is_empty());

    // With BarrierMode::Ignore, B can see A
    let ancestors = service.collect_ancestors(b_id, BarrierMode::Ignore);
    assert_eq!(ancestors.len(), 1);
    assert_eq!(ancestors[0].id, a_id);
}

// ==================== collect_descendants tests ====================

#[test]
fn collect_descendants_no_children() {
    let cfg = StaticTrPluginConfig {
        tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");
    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());

    let descendants = service.collect_descendants(a_id, &[], BarrierMode::Respect, None);
    assert!(descendants.is_empty());
}

#[test]
fn collect_descendants_linear_hierarchy() {
    // A -> B -> C
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_with_parent(TENANT_B, "Child", TENANT_A),
            tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    // Descendants of A (unlimited depth)
    let descendants = service.collect_descendants(a_id, &[], BarrierMode::Respect, None);
    assert_eq!(descendants.len(), 2);
    // Pre-order: B first, then C
    assert_eq!(descendants[0].id, b_id);
    assert_eq!(descendants[1].id, c_id);

    // Descendants of A (depth 1 = direct children only)
    let descendants = service.collect_descendants(a_id, &[], BarrierMode::Respect, Some(1));
    assert_eq!(descendants.len(), 1);
    assert_eq!(descendants[0].id, b_id);
}

#[test]
fn collect_descendants_with_barrier() {
    // A -> B (barrier) -> C
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_barrier(TENANT_B, "Barrier", TENANT_A),
            tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    // With BarrierMode::Respect, descendants of A exclude B (barrier) and its subtree
    let descendants = service.collect_descendants(a_id, &[], BarrierMode::Respect, None);
    assert!(descendants.is_empty());

    // With BarrierMode::Ignore, descendants include B and C
    let descendants = service.collect_descendants(a_id, &[], BarrierMode::Ignore, None);
    assert_eq!(descendants.len(), 2);
    assert_eq!(descendants[0].id, b_id);
    assert_eq!(descendants[1].id, c_id);
}

#[test]
fn collect_descendants_mixed_barrier() {
    // A -> B (barrier) -> C
    //   -> D (no barrier)
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_barrier(TENANT_B, "Barrier", TENANT_A),
            tenant_with_parent(TENANT_C, "Under Barrier", TENANT_B),
            tenant_with_parent(TENANT_D, "Normal Child", TENANT_A),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let d_id = TenantId(Uuid::parse_str(TENANT_D).unwrap());

    // With BarrierMode::Respect, only D is visible
    let descendants = service.collect_descendants(a_id, &[], BarrierMode::Respect, None);
    assert_eq!(descendants.len(), 1);
    assert_eq!(descendants[0].id, d_id);
}

// ==================== is_ancestor_of tests ====================

#[test]
fn is_ancestor_of_self() {
    let cfg = StaticTrPluginConfig {
        tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");
    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());

    // Self is NOT an ancestor of self
    assert!(
        !service
            .is_ancestor_of(a_id, a_id, BarrierMode::Respect)
            .unwrap()
    );
}

#[test]
fn is_ancestor_of_direct_parent() {
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_with_parent(TENANT_B, "Child", TENANT_A),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());

    assert!(
        service
            .is_ancestor_of(a_id, b_id, BarrierMode::Respect)
            .unwrap()
    );
    assert!(
        !service
            .is_ancestor_of(b_id, a_id, BarrierMode::Respect)
            .unwrap()
    );
}

#[test]
fn is_ancestor_of_grandparent() {
    // A -> B -> C
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_with_parent(TENANT_B, "Child", TENANT_A),
            tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    assert!(
        service
            .is_ancestor_of(a_id, c_id, BarrierMode::Respect)
            .unwrap()
    );
}

#[test]
fn is_ancestor_of_with_barrier() {
    // A -> B (barrier) -> C
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_barrier(TENANT_B, "Barrier", TENANT_A),
            tenant_with_parent(TENANT_C, "Grandchild", TENANT_B),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    // B is direct parent of C - no barrier crossed
    assert!(
        service
            .is_ancestor_of(b_id, c_id, BarrierMode::Respect)
            .unwrap()
    );

    // A is blocked by barrier B
    assert!(
        !service
            .is_ancestor_of(a_id, c_id, BarrierMode::Respect)
            .unwrap()
    );

    // With BarrierMode::Ignore, A is ancestor of C
    assert!(
        service
            .is_ancestor_of(a_id, c_id, BarrierMode::Ignore)
            .unwrap()
    );
}

#[test]
fn is_ancestor_of_direct_barrier_child() {
    // A -> B (barrier)
    // is_ancestor(A, B) should return false because B is a barrier
    // (consistent with get_descendants(A) excluding B)
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_barrier(TENANT_B, "Barrier", TENANT_A),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());

    // A is NOT ancestor of B when B is a barrier (BarrierMode::Respect)
    assert!(
        !service
            .is_ancestor_of(a_id, b_id, BarrierMode::Respect)
            .unwrap()
    );

    // With BarrierMode::Ignore, A IS ancestor of B
    assert!(
        service
            .is_ancestor_of(a_id, b_id, BarrierMode::Ignore)
            .unwrap()
    );

    // B is NOT ancestor of itself (self is not an ancestor of self)
    assert!(
        !service
            .is_ancestor_of(b_id, b_id, BarrierMode::Respect)
            .unwrap()
    );
}

#[test]
fn is_ancestor_of_nonexistent() {
    let cfg = StaticTrPluginConfig {
        tenants: vec![tenant(TENANT_A, "Root", TenantStatus::Active)],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let nonexistent = TenantId(Uuid::parse_str(TENANT_B).unwrap());

    // Nonexistent ancestor
    assert!(matches!(
        service.is_ancestor_of(nonexistent, a_id, BarrierMode::Respect),
        Err(TenantResolverError::TenantNotFound { tenant_id }) if tenant_id == nonexistent
    ));

    // Nonexistent descendant
    assert!(matches!(
        service.is_ancestor_of(a_id, nonexistent, BarrierMode::Respect),
        Err(TenantResolverError::TenantNotFound { tenant_id }) if tenant_id == nonexistent
    ));
}

// Cycle termination used to be tested here, but `StaticTrPluginConfig::validate`
// now rejects disconnected cycles at load time (see `validate_rejects_disconnected_cycle`
// in config_tests.rs), so no cycle-shaped `Service` can be built through
// `Service::from_config`. The `visited` guards in `collect_ancestors` and
// `is_ancestor_of` remain as defense-in-depth but are unreachable from the
// public API.

#[test]
fn is_ancestor_of_unrelated() {
    // A is the root; B and C are siblings under A (unrelated to each other).
    let cfg = StaticTrPluginConfig {
        tenants: vec![
            tenant(TENANT_A, "Root", TenantStatus::Active),
            tenant_with_parent(TENANT_B, "Sibling B", TENANT_A),
            tenant_with_parent(TENANT_C, "Sibling C", TENANT_A),
        ],
        ..Default::default()
    };
    let service = Service::from_config(&cfg).expect("valid config");

    let a_id = TenantId(Uuid::parse_str(TENANT_A).unwrap());
    let b_id = TenantId(Uuid::parse_str(TENANT_B).unwrap());
    let c_id = TenantId(Uuid::parse_str(TENANT_C).unwrap());

    // Siblings are not ancestors of each other.
    assert!(
        !service
            .is_ancestor_of(b_id, c_id, BarrierMode::Respect)
            .unwrap()
    );
    // A child is not an ancestor of the root.
    assert!(
        !service
            .is_ancestor_of(b_id, a_id, BarrierMode::Respect)
            .unwrap()
    );
}
