"""Tests for the Achievement Gallery — profile_builder.compute_achievements().

All tests use the temp_db fixture (auto-use in conftest.py) which wires
BIGMIND_DB_PATH + BIGMIND_USER to a fresh SQLite file per test.
"""

import pytest
from datetime import datetime, timezone, timedelta
from bigmind import memory_store
from bigmind.db import db
from bigmind.profile_builder import compute_achievements, build_profile_data


# ── Helpers ───────────────────────────────────────────────────────────────────

def _uid():
    user = memory_store.get_or_create_user(memory_store.get_current_username())
    return user["id"]


def _close_session(session_id: str, has_tier2: bool = False):
    """Close a session with a one-liner summary."""
    memory_store.close_session(
        session_id=session_id,
        one_liner="test session",
        topics="test",
        outcome="ok",
        importance=5,
    )
    if has_tier2:
        memory_store.save_session_summary(session_id, summary="detailed summary")


# ── TestComputeAchievements ───────────────────────────────────────────────────

class TestComputeAchievements:

    def test_returns_list_of_expected_ids(self):
        uid = _uid()
        achievements = compute_achievements(uid)
        ids = {a["id"] for a in achievements}
        expected = {
            "first_breath", "first_thought", "eureka", "honest_mind",
            "scholar", "deep_knowledge", "scientist", "veteran",
            "on_fire", "storyteller", "night_owl", "speed_thinker",
            "first_handshake", "birthday", "shared_mind",
            "frugal_mind", "quarter_million", "token_millionaire", "sniper",
            "networker_bronze", "networker_silver", "networker_gold", "networker_platinum",
            "tokensniper_bronze", "tokensniper_silver", "tokensniper_gold", "tokensniper_platinum",
            "hypothesismaster_bronze", "hypothesismaster_silver", "hypothesismaster_gold", "hypothesismaster_platinum",
            "memoryarchitect_bronze", "memoryarchitect_silver", "memoryarchitect_gold", "memoryarchitect_platinum",
            "sessionveteran_bronze", "sessionveteran_silver", "sessionveteran_gold", "sessionveteran_platinum",
        }
        assert expected == ids

    def test_all_locked_for_empty_db(self):
        """Fresh DB: most achievements locked, except First Handshake (hardcoded)."""
        uid = _uid()
        achievements = compute_achievements(uid)
        by_id = {a["id"]: a for a in achievements}

        # First Handshake is always unlocked (hardcoded to 2026-03-31)
        assert by_id["first_handshake"]["unlocked"] is True
        assert by_id["first_handshake"]["unlocked_at"] == "2026-03-31"

        # Everything else locked
        for aid in ["first_breath", "first_thought", "eureka", "honest_mind",
                    "scholar", "veteran", "on_fire", "storyteller", "night_owl",
                    "speed_thinker", "frugal_mind", "quarter_million",
                    "token_millionaire", "sniper"]:
            assert by_id[aid]["unlocked"] is False, f"{aid} should be locked"

        # Shared Mind is always locked (Phase 3 not yet)
        assert by_id["shared_mind"]["unlocked"] is False

    # ── First Breath ──────────────────────────────────────────────────────────

    def test_first_breath_unlocks_after_first_session(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        _close_session(sid)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["first_breath"]["unlocked"] is True
        assert ach["first_breath"]["unlocked_at"] is not None

    def test_first_breath_locked_with_only_open_session(self):
        """Open (unclosed) session does NOT unlock First Breath."""
        uid = _uid()
        memory_store.create_session(uid)  # not closed
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["first_breath"]["unlocked"] is False

    # ── First Thought / Eureka / Honest Mind ─────────────────────────────────

    def test_first_thought_unlocks_on_first_hypothesis(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        memory_store.add_hypothesis(uid, sid, "test hypothesis", confidence=0.7)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["first_thought"]["unlocked"] is True

    def test_eureka_locked_until_confirmed(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        hid = memory_store.add_hypothesis(uid, sid, "will be confirmed")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["eureka"]["unlocked"] is False  # still open

        memory_store.resolve_hypothesis(hid, uid, "confirmed", "yes it was true")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["eureka"]["unlocked"] is True

    def test_honest_mind_locked_until_refuted(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        hid = memory_store.add_hypothesis(uid, sid, "will be refuted")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["honest_mind"]["unlocked"] is False

        memory_store.resolve_hypothesis(hid, uid, "refuted", "nope, was wrong")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["honest_mind"]["unlocked"] is True

    # ── Scholar ───────────────────────────────────────────────────────────────

    def test_scholar_locks_below_25_facts(self):
        uid = _uid()
        for i in range(24):
            memory_store.store_fact(uid, "test", f"fact number {i}")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["scholar"]["unlocked"] is False
        assert "24" in ach["scholar"]["condition"]

    def test_scholar_unlocks_at_25_facts(self):
        uid = _uid()
        for i in range(25):
            memory_store.store_fact(uid, "test", f"fact number {i}")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["scholar"]["unlocked"] is True
        assert ach["scholar"]["unlocked_at"] is not None

    def test_deep_knowledge_unlocks_at_100_facts(self):
        uid = _uid()
        for i in range(100):
            memory_store.store_fact(uid, "test", f"fact number {i}")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["deep_knowledge"]["unlocked"] is True
        # Scholar should also be unlocked
        assert ach["scholar"]["unlocked"] is True

    # ── Scientist ─────────────────────────────────────────────────────────────

    def test_scientist_unlocks_at_10_hypotheses(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        for i in range(9):
            memory_store.add_hypothesis(uid, sid, f"hypothesis {i}")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["scientist"]["unlocked"] is False

        memory_store.add_hypothesis(uid, sid, "hypothesis 9")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["scientist"]["unlocked"] is True
        assert ach["scientist"]["unlocked_at"] is not None

    # ── Veteran ───────────────────────────────────────────────────────────────

    def test_veteran_unlocks_at_50_sessions(self):
        uid = _uid()
        for _ in range(50):
            sid = memory_store.create_session(uid)
            _close_session(sid)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["veteran"]["unlocked"] is True
        assert ach["veteran"]["unlocked_at"] is not None

    def test_veteran_locks_at_49_sessions(self):
        uid = _uid()
        for _ in range(49):
            sid = memory_store.create_session(uid)
            _close_session(sid)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["veteran"]["unlocked"] is False

    # ── On Fire ───────────────────────────────────────────────────────────────

    def test_on_fire_locked_below_5_sessions_per_day(self):
        uid = _uid()
        for _ in range(4):
            sid = memory_store.create_session(uid)
            _close_session(sid)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["on_fire"]["unlocked"] is False

    def test_on_fire_unlocks_at_5_sessions_same_day(self):
        uid = _uid()
        for _ in range(5):
            sid = memory_store.create_session(uid)
            _close_session(sid)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["on_fire"]["unlocked"] is True

    # ── Storyteller ───────────────────────────────────────────────────────────

    def test_storyteller_requires_20_tier2_sessions(self):
        uid = _uid()
        # 19 sessions with tier2
        for _ in range(19):
            sid = memory_store.create_session(uid)
            _close_session(sid, has_tier2=True)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["storyteller"]["unlocked"] is False

        sid = memory_store.create_session(uid)
        _close_session(sid, has_tier2=True)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["storyteller"]["unlocked"] is True

    # ── Speed Thinker ─────────────────────────────────────────────────────────

    def test_speed_thinker_unlocks_same_day_confirm(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        hid = memory_store.add_hypothesis(uid, sid, "quick thought")
        memory_store.resolve_hypothesis(hid, uid, "confirmed", "yes!")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["speed_thinker"]["unlocked"] is True

    # ── Token achievements ────────────────────────────────────────────────────

    def test_frugal_mind_unlocks_on_first_token_save(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        memory_store.log_token_save(sid, uid, "saved tokens by grep", 5000, "grep")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["frugal_mind"]["unlocked"] is True
        assert ach["frugal_mind"]["unlocked_at"] is not None

    def test_quarter_million_unlocks_at_250k(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        memory_store.log_token_save(sid, uid, "big save", 250_000, "grep")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["quarter_million"]["unlocked"] is True

    def test_token_millionaire_unlocks_at_1m(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        memory_store.log_token_save(sid, uid, "huge save", 1_000_000, "memory_hit")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["token_millionaire"]["unlocked"] is True

    def test_sniper_requires_single_save_over_500k(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        # Multiple saves that total > 500k but none individual exceeds it
        memory_store.log_token_save(sid, uid, "save 1", 300_000, "grep")
        memory_store.log_token_save(sid, uid, "save 2", 300_000, "grep")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["sniper"]["unlocked"] is False  # no single save > 500k

        memory_store.log_token_save(sid, uid, "sniper shot", 600_000, "grep")
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["sniper"]["unlocked"] is True

    # ── Birthday ──────────────────────────────────────────────────────────────

    def test_birthday_locked_shows_countdown(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        _close_session(sid)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        bday = ach["birthday"]
        assert bday["unlocked"] is False
        assert bday["extra"] is not None
        assert "In " in bday["extra"] or "day" in bday["extra"]

    # ── Hardcoded achievements ─────────────────────────────────────────────────

    def test_first_handshake_always_unlocked(self):
        uid = _uid()
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["first_handshake"]["unlocked"] is True
        assert ach["first_handshake"]["unlocked_at"] == "2026-03-31"

    def test_shared_mind_always_locked(self):
        uid = _uid()
        ach = {a["id"]: a for a in compute_achievements(uid)}
        assert ach["shared_mind"]["unlocked"] is False

    # ── Achievement structure ─────────────────────────────────────────────────

    def test_all_achievements_have_required_keys(self):
        uid = _uid()
        achievements = compute_achievements(uid)
        for a in achievements:
            assert "id" in a
            assert "icon" in a
            assert "name" in a
            assert "description" in a
            assert "unlocked" in a
            assert "unlocked_at" in a
            assert "condition" in a

    def test_unlocked_achievement_has_no_extra_for_non_birthday(self):
        """Non-birthday unlocked achievements should not have 'extra' countdown text."""
        uid = _uid()
        sid = memory_store.create_session(uid)
        _close_session(sid)
        ach = {a["id"]: a for a in compute_achievements(uid)}
        fb = ach["first_breath"]
        assert fb["unlocked"] is True
        assert fb.get("extra") is None

    # ── build_profile_data integration ────────────────────────────────────────

    def test_build_profile_data_includes_achievements(self):
        uid = _uid()
        data = build_profile_data(uid)
        assert "achievements" in data
        assert isinstance(data["achievements"], list)
        assert len(data["achievements"]) > 0

    def test_build_profile_data_achievement_count_correct(self):
        uid = _uid()
        # Add one session so first_breath and on_fire can unlock
        sid = memory_store.create_session(uid)
        _close_session(sid)
        data = build_profile_data(uid)
        unlocked = [a for a in data["achievements"] if a["unlocked"]]
        # At minimum: first_breath + first_handshake = 2
        assert len(unlocked) >= 2

class TestTieredAchievements:
    def test_networker_bronze(self):
        uid = _uid()
        with db() as conn:
            conn.execute("INSERT INTO people (user_id, username) VALUES (?, ?)", (uid, "test"))
            conn.commit()
        achs = compute_achievements(uid)
        bronze = next(a for a in achs if a['id'] == 'networker_bronze')
        assert bronze['unlocked'] is True
        assert bronze['image'].endswith('networker_bronze.png')

    def test_tokensniper_silver(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        memory_store.log_token_save(sid, uid, "big save", 60000, "grep")
        achs = compute_achievements(uid)
        silver = next(a for a in achs if a['id'] == 'tokensniper_silver')
        assert silver['unlocked'] is True

    def test_hypothesismaster_bronze(self):
        uid = _uid()
        sid = memory_store.create_session(uid)
        for _ in range(3):
            hid = memory_store.add_hypothesis(uid, sid, "test", 0.8)
            memory_store.resolve_hypothesis(hid, uid, "confirmed", "yes")
        achs = compute_achievements(uid)
        bronze = next(a for a in achs if a['id'] == 'hypothesismaster_bronze')
        assert bronze['unlocked'] is True

    def test_memoryarchitect_silver(self):
        uid = _uid()
        for _ in range(100):
            memory_store.store_fact(uid, "test", f"fact {_}")
        achs = compute_achievements(uid)
        silver = next(a for a in achs if a['id'] == 'memoryarchitect_silver')
        assert silver['unlocked'] is True

    def test_sessionveteran_bronze(self):
        uid = _uid()
        for _ in range(50):
            sid = memory_store.create_session(uid)
            _close_session(sid)
        achs = compute_achievements(uid)
        bronze = next(a for a in achs if a['id'] == 'sessionveteran_bronze')
        assert bronze['unlocked'] is True

    def test_tiered_achievements_have_image(self):
        uid = _uid()
        achs = compute_achievements(uid)
        tiered_ids = [
            f"{cat}_{tier}" for cat in ["networker", "tokensniper", "hypothesismaster", "memoryarchitect", "sessionveteran"]
            for tier in ["bronze", "silver", "gold", "platinum"]
        ]
        for tid in tiered_ids:
            a = next(aa for aa in achs if aa['id'] == tid)
            assert a['image'] is not None
            assert a['image'].endswith(tid + '.png')
