# retoor <retoor@molodetz.nl>

from devplacepy.database import (
    get_table,
    get_primary_admin_uid,
    invalidate_admins_cache,
)
from devplacepy.utils import (
    award_badge,
    award_xp,
    get_badge,
    check_milestone_badges,
    create_mention_notifications,
    extract_mentions,
    generate_uid,
    hash_password,
    is_primary_admin,
    level_for_xp,
    safe_next,
    slugify,
    strip_html,
    time_ago,
    verify_password,
)
from datetime import datetime, timedelta, timezone
def _seed_user(role="Member", xp=0, level=1):
    uid = generate_uid()
    username = f"ut_{uid[:8]}"
    get_table("users").insert(
        {
            "uid": uid,
            "username": username,
            "terms_version": "1",
            "email": f"{username}@t.dev",
            "role": role,
            "xp": xp,
            "level": level,
            "created_at": datetime.now(timezone.utc).isoformat(),
        }
    )
    return uid, username


def test_hash_password_returns_string():
    result = hash_password("test123")
    assert isinstance(result, str)
    assert len(result) > 20


def test_verify_password_correct():
    hashed = hash_password("correct-password")
    assert verify_password("correct-password", hashed) is True


def test_verify_password_wrong():
    hashed = hash_password("correct-password")
    assert verify_password("wrong-password", hashed) is False


def test_generate_uid_is_unique():
    uid1 = generate_uid()
    uid2 = generate_uid()
    assert uid1 != uid2


def test_generate_uid_format():
    uid = generate_uid()
    assert isinstance(uid, str)
    assert len(uid) == 36  # UUID v4 format


def test_slugify_basic():
    assert slugify("Hello World") == "hello-world"


def test_slugify_special_chars():
    assert slugify("Hello! World???") == "hello-world"


def test_slugify_multiple_dashes():
    assert slugify("hello---world") == "hello-world"


def test_slugify_leading_trailing():
    assert slugify("--hello--") == "hello"


def test_time_ago_just_now():
    now = datetime.now(timezone.utc).isoformat()
    result = time_ago(now)
    assert result == "just now"


def test_time_ago_minutes():
    dt = (datetime.now(timezone.utc) - timedelta(minutes=5)).isoformat()
    result = time_ago(dt)
    assert "m ago" in result


def test_time_ago_hours():
    dt = (datetime.now(timezone.utc) - timedelta(hours=3)).isoformat()
    result = time_ago(dt)
    assert "h ago" in result


def test_time_ago_days():
    dt = (datetime.now(timezone.utc) - timedelta(days=5)).isoformat()
    result = time_ago(dt)
    assert "d ago" in result


def test_time_ago_months():
    dt = datetime.now(timezone.utc) - timedelta(days=60)
    result = time_ago(dt.isoformat())
    assert result == dt.strftime("%d/%m/%Y")


def test_time_ago_years():
    dt = datetime.now(timezone.utc) - timedelta(days=400)
    result = time_ago(dt.isoformat())
    assert result == dt.strftime("%d/%m/%Y")


def test_level_for_xp_boundaries():
    assert level_for_xp(0) == 1
    assert level_for_xp(99) == 1
    assert level_for_xp(100) == 2
    assert level_for_xp(250) == 3
    assert level_for_xp(450) == 5


def test_level_for_xp_negative():
    assert level_for_xp(-50) == 1


def test_badge_info_known():
    meta = get_badge("Star Author")
    assert meta["icon"]
    assert meta["description"] == "Earned 100 stars"


def test_badge_info_unknown_fallback():
    meta = get_badge("Nonexistent Badge")
    assert meta["icon"]
    assert meta["description"] == "Nonexistent Badge"


def test_safe_next_allows_internal_rejects_external():
    assert safe_next("/gists") == "/gists"
    assert safe_next("//evil.com") == "/feed"
    assert safe_next("http://evil.com") == "/feed"
    assert safe_next(None) == "/feed"


def test_strip_html_removes_tags_and_unescapes():
    assert strip_html("<b>Hi</b> &amp; bye") == "Hi & bye"
    assert strip_html("") == ""


def test_extract_mentions_finds_usernames():
    mentions = extract_mentions("hi @alice and (@bob_1), mail a@b.com")
    assert mentions == ["alice", "bob_1"]


def _seed_user_at(role, created_at):
    uid = generate_uid()
    get_table("users").insert(
        {
            "uid": uid,
            "username": f"pa_{uid[:8]}",
            "terms_version": "1",
            "email": f"pa_{uid[:8]}@t.dev",
            "role": role,
            "created_at": created_at.isoformat(),
            # every real account is created active; the primary administrator must be
            # an account that can actually authenticate
            "is_active": True,
            "deleted_at": None,
        }
    )
    invalidate_admins_cache()
    return get_table("users").find_one(uid=uid)


def _demote_existing_admins():
    existing = [r["uid"] for r in get_table("users").find(role="Admin")]
    for uid in existing:
        get_table("users").update({"uid": uid, "role": "Member"}, ["uid"])
    invalidate_admins_cache()
    return existing


def _restore_admins(uids):
    for uid in uids:
        get_table("users").update({"uid": uid, "role": "Admin"}, ["uid"])
    invalidate_admins_cache()


def _purge(*rows):
    for row in rows:
        get_table("users").delete(uid=row["uid"])


def test_primary_admin_is_earliest_admin(local_db):
    restore = _demote_existing_admins()
    member = admin_first = admin_second = None
    try:
        base = datetime.now(timezone.utc)
        member = _seed_user_at("Member", base)
        admin_first = _seed_user_at("Admin", base + timedelta(seconds=1))
        admin_second = _seed_user_at("Admin", base + timedelta(seconds=2))

        assert get_primary_admin_uid() == admin_first["uid"]
        assert is_primary_admin(admin_first) is True
        assert is_primary_admin(admin_second) is False
        assert is_primary_admin(member) is False
        assert is_primary_admin(None) is False
    finally:
        _purge(*[r for r in (member, admin_first, admin_second) if r])
        _restore_admins(restore)


def test_primary_admin_reassigns_when_founder_demoted(local_db):
    restore = _demote_existing_admins()
    admin_first = admin_second = None
    try:
        base = datetime.now(timezone.utc)
        admin_first = _seed_user_at("Admin", base + timedelta(seconds=1))
        admin_second = _seed_user_at("Admin", base + timedelta(seconds=2))
        assert get_primary_admin_uid() == admin_first["uid"]

        get_table("users").update(
            {"uid": admin_first["uid"], "role": "Member"}, ["uid"]
        )
        invalidate_admins_cache()
        assert get_primary_admin_uid() == admin_second["uid"]
        assert (
            is_primary_admin(get_table("users").find_one(uid=admin_second["uid"]))
            is True
        )
        assert (
            is_primary_admin(get_table("users").find_one(uid=admin_first["uid"]))
            is False
        )
    finally:
        _purge(*[r for r in (admin_first, admin_second) if r])
        _restore_admins(restore)


def test_award_badge_is_idempotent(local_db):
    uid, _ = _seed_user()
    assert award_badge(uid, "First Post") is True
    assert award_badge(uid, "First Post") is False


def test_award_xp_levels_up_and_notifies(local_db):
    uid, _ = _seed_user(xp=95, level=1)
    result = award_xp(uid, 10)
    assert result["xp"] == 105
    assert result["level"] == 2
    assert result["leveled_up"] is True
    assert get_table("notifications").count(user_uid=uid, type="level") == 1


def test_check_milestone_badges_awards_prolific(local_db):
    uid, _ = _seed_user()
    posts = get_table("posts")
    for index in range(10):
        post_uid = generate_uid()
        posts.insert(
            {
                "deleted_at": None,
                "deleted_by": None,
                "uid": post_uid,
                "user_uid": uid,
                "slug": f"{post_uid[:8]}-p",
                "title": None,
                "content": f"milestone post {index}",
                "topic": "random",
                "project_uid": None,
                "image": None,
                "stars": 0,
                "created_at": datetime.now(timezone.utc).isoformat(),
            }
        )
    awarded = check_milestone_badges(uid)
    assert "Prolific" in awarded
    assert get_table("badges").find_one(user_uid=uid, badge_name="Prolific") is not None


def test_create_mention_notifications_targets_known_users(local_db):
    actor_uid, actor_name = _seed_user()
    target_uid, target_name = _seed_user()
    create_mention_notifications(
        f"hey @{target_name} and @{actor_name} and @ghost_zzz",
        actor_uid,
        "/posts/x",
    )
    assert get_table("notifications").count(user_uid=target_uid, type="mention") == 1
    assert get_table("notifications").count(user_uid=actor_uid, type="mention") == 0
