"""Cache unit tests — addressing, get/put, aliases, eviction. No CLAP needed."""
import tempfile
from pathlib import Path

import numpy as np
import soundfile as sf

import cache


def _wav(freq=220, secs=1.0, sr=22050):
    t = np.linspace(0, secs, int(secs * sr), endpoint=False)
    y = 0.2 * np.sin(2 * np.pi * freq * t)
    f = tempfile.NamedTemporaryFile(suffix=".wav", delete=False)
    sf.write(f.name, y, sr)
    return f.name


def test_content_id_is_stable_and_decode_invariant():
    a, b = _wav(220), _wav(220)        # identical audio, two files
    c = _wav(330)                      # different audio
    assert cache.content_id(a) == cache.content_id(b)
    assert cache.content_id(a) != cache.content_id(c)
    assert cache.content_id(a).startswith("cid_")


def test_raw_fallback_for_undecodable():
    f = tempfile.NamedTemporaryFile(suffix=".bin", delete=False)
    Path(f.name).write_bytes(b"not audio at all")
    assert cache.content_id(f.name).startswith("raw_")


def test_get_miss_then_put_then_hit():
    cid = "cid_test_emotion"
    assert cache.get(cid, "emotion") is None
    cache.put(cid, "emotion", result={"valence": 0.5, "arousal": -0.1})
    hit = cache.get(cid, "emotion")
    assert hit and hit["result"]["valence"] == 0.5


def test_params_separate_entries():
    cid = "cid_params"
    cache.put(cid, "stems", result={"backend": "demucs"}, params={"model": "htdemucs"})
    cache.put(cid, "stems", result={"backend": "merge"}, params={"model": "merge"})
    assert cache.get(cid, "stems", {"model": "htdemucs"})["result"]["backend"] == "demucs"
    assert cache.get(cid, "stems", {"model": "merge"})["result"]["backend"] == "merge"


def test_hits_increment():
    cid = "cid_hits"
    cache.put(cid, "emotion", result={"x": 1})
    cache.get(cid, "emotion"); cache.get(cid, "emotion")
    row = next(r for r in cache.stats() if r["kind"] == "emotion")
    assert row["hits"] >= 2


def test_alias_resolves():
    cache.put_alias("yt:abc123", "cid_aliased")
    assert cache.resolve_alias("yt:abc123") == "cid_aliased"
    assert cache.resolve_alias("yt:nope") is None


def test_gc_evicts_coldest_to_cap():
    # three big entries; cap forces eviction of the least-recently-accessed
    for i in range(3):
        cache.put(f"cid_big_{i}", "stems", result={"i": i}, bytes_=1000)
    cache.get("cid_big_2", "stems")          # touch #2 → newest access
    res = cache.gc("stems", max_bytes=1500)  # room for ~1 entry
    assert res["evicted"] >= 1
    assert cache.get("cid_big_2", "stems") is not None  # the touched one survives
