"""Tests for runtime day tracking in MemoryIndex.

Execution days (distinct calendar dates when system was active)
replace calendar days in Ebbinghaus decay.
"""

import pytest
import time
from neo_cortex.memory_index import MemoryIndex
from neo_cortex.models import MemoryRecord


@pytest.fixture
def index(tmp_path):
    return MemoryIndex(str(tmp_path / "test.db"))


# --- Runtime day registration ---

def test_register_runtime_day_returns_count(index):
    """First register returns 1."""
    count = index.register_runtime_day()
    assert count == 1


def test_register_runtime_day_idempotent(index):
    """Same day registered twice returns same count."""
    c1 = index.register_runtime_day()
    c2 = index.register_runtime_day()
    assert c1 == c2


def test_register_multiple_days(index):
    """Simulate multiple past days, count increases."""
    index._conn.execute("INSERT OR IGNORE INTO runtime_days (day) VALUES ('2026-01-01')")
    index._conn.execute("INSERT OR IGNORE INTO runtime_days (day) VALUES ('2026-01-02')")
    index._conn.commit()
    count = index.register_runtime_day()  # adds today
    assert count == 3


def test_get_runtime_day_count_empty(index):
    """Before any registration, count is 0."""
    assert index.get_runtime_day_count() == 0


def test_get_runtime_day_count_after_register(index):
    """After registration, count matches."""
    index.register_runtime_day()
    assert index.get_runtime_day_count() == 1


# --- Memory stores runtime day on insert ---

def test_insert_stores_runtime_day(index):
    """Newly inserted memory gets current runtime day count."""
    rd = index.register_runtime_day()
    record = MemoryRecord(
        id="rd1", session_id="s1",
        question="q", answer_preview="a", document="doc",
    )
    index.insert(record)
    dream_data = index.get_all_for_dream()
    assert len(dream_data) == 1
    assert dream_data[0]["created_at_rd"] == rd
    assert dream_data[0]["last_accessed_rd"] == rd


def test_insert_without_register_uses_zero(index):
    """Insert before register_runtime_day stores 0."""
    record = MemoryRecord(
        id="rd_zero", session_id="s1",
        question="q", answer_preview="a", document="doc",
    )
    index.insert(record)
    dream_data = index.get_all_for_dream()
    assert dream_data[0]["created_at_rd"] == 0
    assert dream_data[0]["last_accessed_rd"] == 0


# --- update_last_accessed updates runtime day ---

def test_update_last_accessed_rd(index):
    """update_last_accessed with runtime_day updates last_accessed_rd."""
    index.register_runtime_day()
    record = MemoryRecord(
        id="rd2", session_id="s1",
        question="q", answer_preview="a", document="doc",
    )
    index.insert(record)

    # Simulate a new day
    index._conn.execute("INSERT OR IGNORE INTO runtime_days (day) VALUES ('2099-12-31')")
    index._conn.commit()
    new_rd = index.get_runtime_day_count()

    index.update_last_accessed(record.id, time.time(), runtime_day=new_rd)
    dream_data = index.get_all_for_dream()
    assert dream_data[0]["last_accessed_rd"] == new_rd
    assert dream_data[0]["created_at_rd"] == 1  # unchanged


def test_update_last_accessed_no_rd_leaves_rd_unchanged(index):
    """update_last_accessed without runtime_day does not change last_accessed_rd."""
    index.register_runtime_day()
    record = MemoryRecord(
        id="rd3", session_id="s1",
        question="q", answer_preview="a", document="doc",
    )
    index.insert(record)
    original_rd = index.get_all_for_dream()[0]["last_accessed_rd"]

    index.update_last_accessed(record.id, time.time())
    after_rd = index.get_all_for_dream()[0]["last_accessed_rd"]
    assert after_rd == original_rd


# --- Decay formula with slowdown ---

def test_decay_zero_execution_days():
    """Zero execution days = full energy."""
    from neo_cortex.decay import ebbinghaus_weight
    assert ebbinghaus_weight(0, stability=1.0, slowdown=10) == 1.0


def test_decay_1_execution_day_slowdown_10():
    """1 execution day, slowdown=10 → exp(-0.1) ≈ 0.90."""
    from neo_cortex.decay import ebbinghaus_weight
    w = ebbinghaus_weight(1, stability=1.0, slowdown=10)
    assert abs(w - 0.9048) < 0.01


def test_decay_10_execution_days_slowdown_10():
    """10 execution days, slowdown=10, stability=1 → exp(-1) ≈ 0.37."""
    from neo_cortex.decay import ebbinghaus_weight
    w = ebbinghaus_weight(10, stability=1.0, slowdown=10)
    assert abs(w - 0.3679) < 0.01


def test_decay_30_execution_days_hits_floor():
    """30 execution days, slowdown=10, stability=1 → floor."""
    from neo_cortex.decay import ebbinghaus_weight
    w = ebbinghaus_weight(30, stability=1.0, slowdown=10)
    assert w == 0.05


def test_decay_high_stability_resists():
    """High stability resists decay over 30 execution days."""
    from neo_cortex.decay import ebbinghaus_weight
    w = ebbinghaus_weight(30, stability=5.0, slowdown=10)
    assert w > 0.5  # exp(-30/10/5) = exp(-0.6) ≈ 0.55


def test_decay_backward_compat_default_slowdown():
    """Default slowdown=1 behaves like original formula."""
    from neo_cortex.decay import ebbinghaus_weight
    w = ebbinghaus_weight(3, stability=1.0)
    assert abs(w - 0.0498) < 0.01  # exp(-3)
