"""
test_pending_flow.py — Success test for Step 5: /pending counterparty flow

Tests (no live Telegram connection needed):
  ✅ 1. pending_coordinations dict is populated when User A coordinates
  ✅ 2. /pending lookup by counterparty username returns correct entries
  ✅ 3. Personality is stored in the pending entry
  ✅ 4. Accepting a request marks it as "accepted" and stores neg_id in user_data
  ✅ 5. Declining a request removes it from pending_coordinations
  ✅ 6. receive_counterparty_preferences persists BOTH participants in DB
  ✅ 7. Both participants have personality_used saved in participants table
  ✅ 8. Negotiation is created in DB with correct feature_type
  ✅ 9. Bot module imports cleanly (all handlers registered)
  ✅ 10. send_to_user silently fails when no bots are registered

Run from project root:
    cd e:\\negot8
    python test/test_pending_flow.py
"""
import sys
import os
import asyncio
import json

# ─── Path setup ───
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
BACKEND = os.path.join(ROOT, "backend")
BOTS_DIR = os.path.join(BACKEND, "telegram-bots")
sys.path.insert(0, BACKEND)
sys.path.insert(0, BOTS_DIR)


# ════════════════════════════════════════════════
# Helpers
# ════════════════════════════════════════════════

PASS = "✅"
FAIL = "❌"
results: list[tuple[str, str, str]] = []  # (status, name, detail)

def record(ok: bool, name: str, detail: str = ""):
    status = PASS if ok else FAIL
    results.append((status, name, detail))
    print(f"  {status}  {name}" + (f"  [{detail}]" if detail else ""))


# ════════════════════════════════════════════════
# Test 1 — Module import & handler registration
# ════════════════════════════════════════════════

def test_module_import():
    print("\n── Test 1: Bot module imports cleanly ──")
    try:
        import bot  # noqa: F401
        record(True, "bot.py imports without errors")
    except Exception as e:
        record(False, "bot.py import", str(e))
        return None

    try:
        from bot import (
            pending_coordinations, bot_apps, register_bot, send_to_user,
            pending_command, accept_pending_callback,
            receive_counterparty_preferences, run_negotiation_with_telegram_updates,
            create_bot, AWAITING_PREFERENCES, AWAITING_COUNTERPARTY_PREFS,
        )
        record(True, "All new symbols exported from bot.py")
        return True
    except ImportError as e:
        record(False, "Symbol import check", str(e))
        return False


# ════════════════════════════════════════════════
# Test 2 — pending_coordinations dict logic
# ════════════════════════════════════════════════

async def test_pending_dict_logic():
    print("\n── Test 2: pending_coordinations dict logic ──")
    import bot

    # Reset shared dict for a clean test
    bot.pending_coordinations.clear()

    NEG_ID      = "test1234"
    PREFS_A     = {"feature_type": "scheduling", "goal": "coffee next week",
                   "constraints": [], "preferences": []}
    PERSONALITY = "aggressive"

    # Simulate User A calling /coordinate and storing in dict (mirrors receive_preferences)
    bot.pending_coordinations[NEG_ID] = {
        "negotiation_id": NEG_ID,
        "counterparty_username": "bob",     # lowercase username
        "initiator_id": 111,
        "initiator_name": "Alice",
        "feature_type": "scheduling",
        "preferences_a": PREFS_A,
        "personality_a": PERSONALITY,
        "status": "pending",
    }

    # /pending lookup: User B (bob) checks for their requests
    username_b = "bob"
    matching = {
        nid: d for nid, d in bot.pending_coordinations.items()
        if d.get("counterparty_username", "").lower() == username_b
        and d.get("status") == "pending"
    }
    record(len(matching) == 1, "/pending finds the request by counterparty username",
           f"found {len(matching)} entry(ies)")

    # Personality is stored
    entry = matching[NEG_ID]
    record(entry.get("personality_a") == PERSONALITY, "Personality_a stored in pending dict",
           entry.get("personality_a"))

    # Accept: mark as accepted, simulating accept_pending_callback
    bot.pending_coordinations[NEG_ID]["status"] = "accepted"
    still_pending = {
        nid: d for nid, d in bot.pending_coordinations.items()
        if d.get("counterparty_username", "").lower() == username_b
        and d.get("status") == "pending"
    }
    record(len(still_pending) == 0,
           "After accept, /pending no longer shows the same request")

    # Decline: should remove entry
    bot.pending_coordinations["test9999"] = {
        "negotiation_id": "test9999",
        "counterparty_username": "carol",
        "initiator_id": 222,
        "initiator_name": "Dave",
        "feature_type": "expenses",
        "preferences_a": {},
        "personality_a": "balanced",
        "status": "pending",
    }
    bot.pending_coordinations.pop("test9999", None)
    record("test9999" not in bot.pending_coordinations, "Declined request removed from dict")


# ════════════════════════════════════════════════
# Test 3 — Database: both participants + personality persisted
# ════════════════════════════════════════════════

async def test_db_persistence():
    print("\n── Test 3: DB persistence of both participants ──")
    import database as db

    await db.init_db()

    NEG_ID = "pendtest1"
    USER_A  = 10001
    USER_B  = 10002

    PREFS_A = {"feature_type": "scheduling", "goal": "coffee meeting",
               "constraints": [], "preferences": []}
    PREFS_B = {"feature_type": "scheduling", "goal": "afternoon slot",
               "constraints": [], "preferences": []}

    # Ensure users exist
    await db.create_user(USER_A, "alice_test", "Alice")
    await db.create_user(USER_B, "bob_test",   "Bob")

    # Create negotiation + add both participants (mirrors the full flow)
    await db.create_negotiation.__wrapped__(NEG_ID, "scheduling", USER_A) \
        if hasattr(db.create_negotiation, "__wrapped__") else None

    # Use raw DB insert for test isolation
    import aiosqlite
    from config import DATABASE_PATH

    async with aiosqlite.connect(DATABASE_PATH) as conn:
        await conn.execute(
            "INSERT OR REPLACE INTO negotiations (id, feature_type, initiator_id) VALUES (?, ?, ?)",
            (NEG_ID, "scheduling", USER_A)
        )
        await conn.commit()

    await db.add_participant(NEG_ID, USER_A, PREFS_A, personality_used="aggressive")
    await db.add_participant(NEG_ID, USER_B, PREFS_B, personality_used="empathetic")

    participants = await db.get_participants(NEG_ID)
    record(len(participants) == 2, "Both participants stored in DB", f"count={len(participants)}")

    personalities = {dict(p)["user_id"]: dict(p)["personality_used"] for p in participants}
    record(personalities.get(USER_A) == "aggressive",
           "User A personality_used='aggressive' persisted", str(personalities.get(USER_A)))
    record(personalities.get(USER_B) == "empathetic",
           "User B personality_used='empathetic' persisted", str(personalities.get(USER_B)))

    # Cleanup
    import aiosqlite
    from config import DATABASE_PATH
    async with aiosqlite.connect(DATABASE_PATH) as conn:
        await conn.execute("DELETE FROM participants WHERE negotiation_id = ?", (NEG_ID,))
        await conn.execute("DELETE FROM negotiations  WHERE id = ?", (NEG_ID,))
        await conn.commit()


# ════════════════════════════════════════════════
# Test 4 — send_to_user fails gracefully when no bots registered
# ════════════════════════════════════════════════

async def test_send_to_user_graceful():
    print("\n── Test 4: send_to_user graceful failure ──")
    import bot

    bot.bot_apps.clear()   # No bots registered
    result = await bot.send_to_user(99999, "test message")
    record(result is False, "send_to_user returns False when no bots are registered")


# ════════════════════════════════════════════════
# Test 5 — Conversation state constants are correct
# ════════════════════════════════════════════════

def test_conversation_states():
    print("\n── Test 5: ConversationHandler state constants ──")
    from bot import AWAITING_PREFERENCES, AWAITING_COUNTERPARTY_PREFS
    record(AWAITING_PREFERENCES == 1,
           "AWAITING_PREFERENCES == 1", str(AWAITING_PREFERENCES))
    record(AWAITING_COUNTERPARTY_PREFS == 2,
           "AWAITING_COUNTERPARTY_PREFS == 2", str(AWAITING_COUNTERPARTY_PREFS))
    record(AWAITING_PREFERENCES != AWAITING_COUNTERPARTY_PREFS,
           "States are distinct (no collision)")


# ════════════════════════════════════════════════
# Main runner
# ════════════════════════════════════════════════

async def run_all():
    ok = test_module_import()
    if ok is False:
        print("\n⛔ Aborting — bot.py failed to import. Fix import errors first.")
        return

    await test_pending_dict_logic()
    await test_db_persistence()
    await test_send_to_user_graceful()
    test_conversation_states()

    # ── Summary ──
    passed = sum(1 for s, _, _ in results if s == PASS)
    total  = len(results)
    print("\n" + "=" * 60)
    print(f"  PENDING FLOW TESTS: {passed}/{total} passed")
    print("=" * 60)

    if passed == total:
        print("\n🏆 ALL TESTS PASSED — /pending counterparty flow is ready!\n")
        print("Next steps (live Telegram test):")
        print("  1. Run:  python test/bot_runner_test.py")
        print("  2. Phone A: /coordinate @PhoneB_username")
        print("  3. Phone A: describe your preferences")
        print("  4. Phone B: /pending  → tap Accept")
        print("  5. Phone B: describe their preferences")
        print("  6. Watch agents negotiate live on both phones! 🤖↔️🤖\n")
    else:
        failed = [(name, detail) for s, name, detail in results if s == FAIL]
        print(f"\n⚠️  {total - passed} test(s) failed:")
        for name, detail in failed:
            print(f"     • {name}: {detail}")
        print()


if __name__ == "__main__":
    asyncio.run(run_all())
