"""Tests for VirtualAllocator: device-wide VA space management.

Validates:
  T7. Basic VA allocation (contiguous, non-overlapping)
  T8. VA free + reallocation (free-list reuse)
  T9. VA space exhaustion raises AllocationError
"""
import pytest

from kernbench.policy.address.va_allocator import VirtualAllocator

_KB = 1024
_MB = 1024 * 1024
_GB = 1024 * 1024 * 1024


# ── T7. Basic VA allocation ──────────────────────────────────────────


def test_alloc_returns_aligned_va():
    """First allocation returns va_base."""
    va = VirtualAllocator(va_base=0x1_0000_0000, va_size=1 * _GB, page_size=4096)
    addr = va.alloc(4096)
    assert addr == 0x1_0000_0000


def test_alloc_sequential_non_overlapping():
    """Two allocations return contiguous, non-overlapping VA ranges."""
    va = VirtualAllocator(va_base=0x1_0000_0000, va_size=1 * _GB, page_size=4096)
    a1 = va.alloc(4096)
    a2 = va.alloc(8192)
    assert a1 == 0x1_0000_0000
    assert a2 == 0x1_0000_1000  # a1 + 4096
    # No overlap
    assert a2 >= a1 + 4096


def test_alloc_page_aligned():
    """Allocations are page-aligned even if requested size is not page-multiple."""
    va = VirtualAllocator(va_base=0x1_0000_0000, va_size=1 * _GB, page_size=4096)
    a1 = va.alloc(100)   # < 1 page, but occupies 1 page
    a2 = va.alloc(100)
    assert a2 == 0x1_0000_1000  # aligned to next page


def test_alloc_large_contiguous():
    """Large allocation (multiple pages) is contiguous."""
    va = VirtualAllocator(va_base=0x0, va_size=1 * _GB, page_size=2 * _MB)
    addr = va.alloc(8 * _MB)  # 4 pages
    assert addr == 0x0
    # Next alloc starts after 8 MB
    addr2 = va.alloc(2 * _MB)
    assert addr2 == 8 * _MB


# ── T8. VA free + reallocation ───────────────────────────────────────


def test_free_and_realloc():
    """Freed VA range can be reused by subsequent allocation."""
    va = VirtualAllocator(va_base=0x1_0000_0000, va_size=1 * _GB, page_size=4096)
    a1 = va.alloc(4096)
    a2 = va.alloc(4096)
    va.free(a1, 4096)

    # New alloc should reuse a1's range
    a3 = va.alloc(4096)
    assert a3 == a1


def test_free_coalesce():
    """Freeing adjacent blocks allows larger reallocation."""
    va = VirtualAllocator(va_base=0x0, va_size=1 * _GB, page_size=4096)
    a1 = va.alloc(4096)
    a2 = va.alloc(4096)
    a3 = va.alloc(4096)

    # Free first two (adjacent)
    va.free(a1, 4096)
    va.free(a2, 4096)

    # Should be able to allocate 8192 contiguous from the freed region
    a4 = va.alloc(8192)
    assert a4 == a1  # reuses coalesced region


def test_free_out_of_order():
    """Free in non-sequential order still works."""
    va = VirtualAllocator(va_base=0x0, va_size=1 * _GB, page_size=4096)
    a1 = va.alloc(4096)
    a2 = va.alloc(4096)
    a3 = va.alloc(4096)

    va.free(a2, 4096)  # free middle
    a4 = va.alloc(4096)
    assert a4 == a2  # reuses middle slot


# ── T9. VA space exhaustion ──────────────────────────────────────────


def test_alloc_exhaustion():
    """Allocation beyond VA space raises AllocationError."""
    va = VirtualAllocator(va_base=0x0, va_size=8192, page_size=4096)
    va.alloc(4096)
    va.alloc(4096)
    with pytest.raises(Exception, match="[Aa]lloc|[Ee]xhaust|[Oo]ut of"):
        va.alloc(4096)


def test_alloc_after_partial_free():
    """After freeing some, can allocate again within freed space."""
    va = VirtualAllocator(va_base=0x0, va_size=8192, page_size=4096)
    a1 = va.alloc(4096)
    a2 = va.alloc(4096)

    # Space is full
    with pytest.raises(Exception):
        va.alloc(4096)

    # Free one, now can allocate again
    va.free(a1, 4096)
    a3 = va.alloc(4096)
    assert a3 == a1


# ── Stats / inspection ───────────────────────────────────────────────


def test_used_and_total():
    """used and total properties reflect allocation state."""
    va = VirtualAllocator(va_base=0x0, va_size=1 * _MB, page_size=4096)
    assert va.used == 0
    assert va.total == 1 * _MB

    va.alloc(4096)
    assert va.used == 4096

    va.alloc(8192)
    assert va.used == 4096 + 8192  # page-aligned: 4096 + 8192 = 12288
