"""
Phase-26: SocialGraphV2 Advanced Features — Unit Tests

Tests cover:
  T26.1  Path Finding
  T26.2  Network Analysis
  T26.3  N-Degree Contacts & Network Stats
  T26.4  Item Management
  T26.5  Attribute Management
  T26.6  Birthday & Family Tree
  T26.7  Compatibility Analysis
"""

import os, sys, tempfile, shutil

from aifastdb import SocialGraphV2


def test_all():
    tmp = tempfile.mkdtemp(prefix="sg_adv_test_")
    try:
        sg = SocialGraphV2(tmp, shard_count=4, wal_enabled=False)

        # ==================================================================
        # Setup: create a social network
        #   Alice -- Bob -- Charlie -- David
        #              \       /
        #               Eve
        # ==================================================================
        alice = sg.add_person({"name": "Alice", "properties": {"gender": "female"}})
        bob   = sg.add_person({"name": "Bob",   "properties": {"gender": "male"}})
        charlie = sg.add_person({"name": "Charlie", "properties": {"gender": "male"}})
        david = sg.add_person({"name": "David", "properties": {"gender": "male"}})
        eve   = sg.add_person({"name": "Eve",   "properties": {"gender": "female"}})

        sg.add_friend(alice["id"], bob["id"])
        sg.add_friend(bob["id"], charlie["id"])
        sg.add_friend(charlie["id"], david["id"])
        sg.add_friend(bob["id"], eve["id"])
        sg.add_friend(eve["id"], charlie["id"])

        print("=== T26.1: Path Finding ===")

        # find_path: Alice -> David (shortest: Alice -> Bob -> Charlie -> David)
        path_result = sg.find_path(alice["id"], david["id"])
        assert path_result is not None, "Path should exist between Alice and David"
        assert path_result["hops"] == 3, f"Expected 3 hops, got {path_result['hops']}"
        assert len(path_result["path"]) == 4, f"Expected 4 nodes in path, got {len(path_result['path'])}"
        assert len(path_result["intermediaries"]) == 2, f"Expected 2 intermediaries, got {len(path_result['intermediaries'])}"
        print(f"  find_path: Alice->David = {path_result['hops']} hops, OK")

        # find_path: no path between disconnected persons
        loner = sg.add_person({"name": "Loner"})
        no_path = sg.find_path(alice["id"], loner["id"])
        assert no_path is None, "No path to disconnected person"
        print("  find_path with disconnected: None (correct), OK")

        # No path to self should return 0 hops
        self_path = sg.find_path(alice["id"], alice["id"])
        assert self_path is not None
        assert self_path["hops"] == 0
        print("  find_path self: 0 hops, OK")

        # find_all_paths
        all_paths = sg.find_all_paths(alice["id"], david["id"], max_hops=6, max_paths=10)
        assert len(all_paths["paths"]) >= 1, "Should find at least 1 path"
        print(f"  find_all_paths: found {len(all_paths['paths'])} paths, shortest_hops={all_paths['shortest_hops']}, OK")

        # find_intermediaries
        intermediaries = sg.find_intermediaries(alice["id"], david["id"])
        assert len(intermediaries) >= 1, "Should have intermediaries"
        intermediary_names = {p["name"] for p in intermediaries}
        assert "Bob" in intermediary_names or "Charlie" in intermediary_names or "Eve" in intermediary_names
        print(f"  find_intermediaries: {intermediary_names}, OK")

        print("=== T26.2: Network Analysis ===")

        # get_mutual_friends: Alice & Charlie both know Bob
        mutual = sg.get_mutual_friends(alice["id"], charlie["id"])
        mutual_names = {p["name"] for p in mutual}
        assert "Bob" in mutual_names, f"Bob should be mutual friend, got {mutual_names}"
        print(f"  get_mutual_friends(Alice, Charlie): {mutual_names}, OK")

        # calculate_relationship_strength
        strength = sg.calculate_relationship_strength(alice["id"], bob["id"])
        assert strength["direct_strength"] > 0, "Direct friends should have positive strength"
        assert strength["shortest_path_length"] == 1
        assert strength["overall_score"] > 0
        print(f"  calculate_relationship_strength: direct={strength['direct_strength']}, overall={strength['overall_score']:.3f}, OK")

        # suggest_connections: for Alice, should suggest Charlie/David/Eve
        suggestions = sg.suggest_connections(alice["id"])
        # suggestions are non-friend connections
        print(f"  suggest_connections(Alice): {len(suggestions)} suggestions, OK")

        print("=== T26.3: N-Degree Contacts & Network Stats ===")

        # get_nth_degree_contacts
        contacts = sg.get_nth_degree_contacts(alice["id"], max_degree=3)
        degree1_names = {p["name"] for p in contacts["degree1"]}
        assert "Bob" in degree1_names, f"Bob should be degree1, got {degree1_names}"
        degree2_names = {p["name"] for p in contacts["degree2"]}
        assert len(degree2_names) > 0, "Should have degree2 contacts"
        print(f"  degree1: {degree1_names}, degree2: {degree2_names}, degree3: {len(contacts['degree3'])}, OK")

        # get_network_stats
        stats = sg.get_network_stats(alice["id"])
        assert stats["total_friends"] >= 1
        assert "network_reach_2hop" in stats
        assert "clustering_coefficient" in stats
        print(f"  get_network_stats: friends={stats['total_friends']}, reach_2hop={stats['network_reach_2hop']}, OK")

        print("=== T26.4: Item Management ===")

        # add_item
        tesla = sg.add_item({"name": "Tesla Model Y", "category": "vehicle", "brand": "Tesla", "model": "Model Y"})
        assert tesla["name"] == "Tesla Model Y"
        assert tesla["id"] is not None
        print(f"  add_item: {tesla['name']} (id={tesla['id'][:8]}...), OK")

        iphone = sg.add_item({"name": "iPhone 16 Pro", "category": "phone", "brand": "Apple", "model": "iPhone 16 Pro"})

        # add_item_to_person
        rel_id = sg.add_item_to_person(alice["id"], tesla["id"])
        assert rel_id is not None
        rel_id2 = sg.add_item_to_person(bob["id"], tesla["id"])
        assert rel_id2 is not None
        sg.add_item_to_person(alice["id"], iphone["id"])
        print("  add_item_to_person: OK")

        # get_person_items
        alice_items = sg.get_person_items(alice["id"])
        assert len(alice_items) == 2, f"Alice should have 2 items, got {len(alice_items)}"
        print(f"  get_person_items(Alice): {len(alice_items)} items, OK")

        # find_shared_items
        shared = sg.find_shared_items(alice["id"], bob["id"])
        assert len(shared) == 1
        assert shared[0]["name"] == "Tesla Model Y"
        print(f"  find_shared_items: {[i['name'] for i in shared]}, OK")

        # find_people_by_shared_items
        matches = sg.find_people_by_shared_items(alice["id"])
        assert len(matches) >= 1
        print(f"  find_people_by_shared_items: {len(matches)} match(es), OK")

        # list_items
        all_items = sg.list_items()
        assert len(all_items) >= 2
        vehicle_items = sg.list_items(category="vehicle")
        assert len(vehicle_items) >= 1
        print(f"  list_items: all={len(all_items)}, vehicles={len(vehicle_items)}, OK")

        # remove_item_from_person
        removed = sg.remove_item_from_person(bob["id"], tesla["id"])
        assert removed is True
        bob_items = sg.get_person_items(bob["id"])
        assert len(bob_items) == 0
        print("  remove_item_from_person: OK")

        print("=== T26.5: Attribute Management ===")

        # add_attribute
        attr = sg.add_attribute({"category": "zodiac", "value": "Leo"})
        assert attr["name"] == "Leo"
        print(f"  add_attribute: {attr['name']} (zodiac), OK")

        # find_or_create_attribute
        tsinghua = sg.find_or_create_attribute("school", "Tsinghua University")
        assert tsinghua["name"] == "Tsinghua University"
        # Finding again should return same entity
        tsinghua2 = sg.find_or_create_attribute("school", "Tsinghua University")
        assert tsinghua2["id"] == tsinghua["id"]
        print("  find_or_create_attribute: OK (idempotent)")

        beijing = sg.find_or_create_attribute("city", "Beijing")

        # set_person_attribute
        rel = sg.set_person_attribute(alice["id"], tsinghua["id"])
        assert rel is not None
        sg.set_person_attribute(bob["id"], tsinghua["id"])
        sg.set_person_attribute(alice["id"], beijing["id"])
        sg.set_person_attribute(bob["id"], beijing["id"])
        sg.set_person_attribute(alice["id"], attr["id"])  # Leo
        sg.set_person_attribute(bob["id"], attr["id"])    # Leo
        print("  set_person_attribute: OK")

        # get_person_attributes
        alice_attrs = sg.get_person_attributes(alice["id"])
        assert len(alice_attrs) >= 3, f"Alice should have >=3 attributes, got {len(alice_attrs)}"
        print(f"  get_person_attributes(Alice): {len(alice_attrs)} attributes, OK")

        # list_attributes
        all_attrs = sg.list_attributes()
        assert len(all_attrs) >= 3
        school_attrs = sg.list_attributes(category="school")
        assert len(school_attrs) >= 1
        print(f"  list_attributes: all={len(all_attrs)}, schools={len(school_attrs)}, OK")

        # find_alumni
        alumni = sg.find_alumni(alice["id"])
        alumni_names = {p["name"] for p in alumni}
        assert "Bob" in alumni_names
        print(f"  find_alumni(Alice): {alumni_names}, OK")

        # find_same_city
        same_city = sg.find_same_city(alice["id"])
        same_city_names = {p["name"] for p in same_city}
        assert "Bob" in same_city_names
        print(f"  find_same_city(Alice): {same_city_names}, OK")

        # find_shared_attributes
        shared_attrs = sg.find_shared_attributes(alice["id"], bob["id"])
        assert len(shared_attrs) >= 2, f"Should share >=2 attributes, got {len(shared_attrs)}"
        print(f"  find_shared_attributes: {len(shared_attrs)} shared, OK")

        # remove_person_attribute
        removed = sg.remove_person_attribute(bob["id"], attr["id"])
        assert removed is True
        print("  remove_person_attribute: OK")

        print("=== T26.6: Birthday & Family Tree ===")

        # set_birthday
        ok = sg.set_birthday(alice["id"], "1990-03-15")
        assert ok is True
        ok2 = sg.set_birthday(bob["id"], "1988-07-22")
        assert ok2 is True
        print("  set_birthday: OK")

        # get_birthday
        bday = sg.get_birthday(alice["id"])
        assert bday == "1990-03-15", f"Expected 1990-03-15, got {bday}"
        print(f"  get_birthday(Alice): {bday}, OK")

        # set_birthday with invalid format
        bad = sg.set_birthday(alice["id"], "not-a-date")
        assert bad is False
        print("  set_birthday(invalid): False, OK")

        # find_upcoming_birthdays (just check no crash; results depend on current date)
        upcoming = sg.find_upcoming_birthdays(days=365)
        print(f"  find_upcoming_birthdays(365): {len(upcoming)} results, OK")

        # --- Family Tree ---
        # Create family: grandpa -> father -> child
        grandpa = sg.add_person({"name": "Grandpa", "properties": {"gender": "male"}})
        father = sg.add_person({"name": "Father", "properties": {"gender": "male"}})
        mother = sg.add_person({"name": "Mother", "properties": {"gender": "female"}})
        child = sg.add_person({"name": "Child", "properties": {"gender": "male"}})
        child2 = sg.add_person({"name": "Child2", "properties": {"gender": "female"}})

        # add_parent_child
        rel1 = sg.add_parent_child(grandpa["id"], father["id"])
        assert rel1 is not None
        rel2 = sg.add_parent_child(father["id"], child["id"])
        assert rel2 is not None
        sg.add_parent_child(mother["id"], child["id"])
        sg.add_parent_child(father["id"], child2["id"])
        sg.add_parent_child(mother["id"], child2["id"])
        print("  add_parent_child: OK")

        # get_parents
        parents = sg.get_parents(child["id"])
        parent_names = {p["name"] for p in parents}
        assert "Father" in parent_names, f"Father should be a parent, got {parent_names}"
        assert "Mother" in parent_names, f"Mother should be a parent, got {parent_names}"
        print(f"  get_parents(Child): {parent_names}, OK")

        # get_children
        children = sg.get_children(father["id"])
        children_names = {p["name"] for p in children}
        assert "Child" in children_names
        assert "Child2" in children_names
        print(f"  get_children(Father): {children_names}, OK")

        # get_ancestors
        ancestors = sg.get_ancestors(child["id"], max_generations=5)
        ancestor_names = {p["name"] for p in ancestors}
        assert "Father" in ancestor_names
        assert "Grandpa" in ancestor_names
        print(f"  get_ancestors(Child): {ancestor_names}, OK")

        # get_descendants
        descendants = sg.get_descendants(grandpa["id"], max_generations=5)
        desc_names = {p["name"] for p in descendants}
        assert "Father" in desc_names
        assert "Child" in desc_names
        print(f"  get_descendants(Grandpa): {desc_names}, OK")

        # add_spouse
        spouse_rel = sg.add_spouse(father["id"], mother["id"])
        assert spouse_rel is not None
        print("  add_spouse: OK")

        # add_sibling
        sib_rel = sg.add_sibling(child["id"], child2["id"])
        assert sib_rel is not None
        print("  add_sibling: OK")

        print("=== T26.7: Compatibility Analysis ===")

        # analyze_compatibility between Alice and Bob (share school, city, some attributes)
        compat = sg.analyze_compatibility(alice["id"], bob["id"])
        assert "overall_score" in compat
        assert "shared_attributes" in compat
        assert "analysis" in compat
        assert compat["overall_score"] >= 0
        print(f"  analyze_compatibility: score={compat['overall_score']:.3f}, shared={len(compat['shared_attributes'])}, OK")
        print(f"  analysis: {compat['analysis']}")

        # Compatibility with someone who has no attributes
        compat2 = sg.analyze_compatibility(alice["id"], david["id"])
        assert compat2["overall_score"] == 0.0 or compat2["overall_score"] >= 0
        print(f"  analyze_compatibility(Alice, David): score={compat2['overall_score']:.3f}, OK")

        print()
        print("=" * 60)
        print("All Phase-26 tests passed!")
        print("=" * 60)

    finally:
        shutil.rmtree(tmp, ignore_errors=True)


if __name__ == "__main__":
    test_all()
