"""
autobee test suite — run with: pytest tests/ -v
"""

import pytest
from autobee import (
    bubble_sort, insertion_sort, selection_sort, merge_sort, quick_sort, heap_sort,
    binary_search, linear_search,
    bfs, dfs, dijkstra,
    fibonacci, knapsack, lcs,
    Stack, Queue, LinkedList, BST,
)

UNSORTED = [5, 3, 8, 1, 9, 2, 7, 4, 6]
SORTED   = [1, 2, 3, 4, 5, 6, 7, 8, 9]

# ─── Sorting ──────────────────────────────────────────────────────────────────

@pytest.mark.parametrize("fn", [bubble_sort, insertion_sort, selection_sort, merge_sort, quick_sort, heap_sort])
def test_sort_result(fn):
    assert fn(UNSORTED)["result"] == SORTED

@pytest.mark.parametrize("fn", [bubble_sort, insertion_sort, selection_sort, merge_sort, quick_sort, heap_sort])
def test_sort_empty(fn):
    assert fn([])["result"] == []

@pytest.mark.parametrize("fn", [bubble_sort, insertion_sort, selection_sort, merge_sort, quick_sort, heap_sort])
def test_sort_single(fn):
    assert fn([42])["result"] == [42]

@pytest.mark.parametrize("fn", [bubble_sort, insertion_sort, selection_sort, merge_sort, quick_sort, heap_sort])
def test_sort_steps_returned(fn):
    r = fn([3, 1, 2])
    assert "steps" in r
    assert isinstance(r["steps"], list)

@pytest.mark.parametrize("fn", [bubble_sort, insertion_sort, selection_sort, merge_sort, quick_sort, heap_sort])
def test_sort_does_not_mutate(fn):
    original = [5, 3, 8, 1]
    copy = original[:]
    fn(original)
    assert original == copy

# ─── Searching ────────────────────────────────────────────────────────────────

def test_binary_search_found():
    r = binary_search([1, 3, 5, 7, 9], 7)
    assert r["found"] is True
    assert r["result"] == 3

def test_binary_search_not_found():
    r = binary_search([1, 3, 5, 7, 9], 6)
    assert r["found"] is False
    assert r["result"] == -1

def test_linear_search_found():
    r = linear_search(["apple", "bee", "cat"], "bee")
    assert r["found"] is True
    assert r["result"] == 1

def test_linear_search_not_found():
    r = linear_search([1, 2, 3], 99)
    assert r["found"] is False

# ─── Graph ────────────────────────────────────────────────────────────────────

GRAPH = {"A": ["B", "C"], "B": ["D"], "C": ["D"], "D": []}

def test_bfs_order():
    r = bfs(GRAPH, "A")
    assert r["result"][0] == "A"
    assert set(r["result"]) == {"A", "B", "C", "D"}

def test_dfs_visits_all():
    r = dfs(GRAPH, "A")
    assert set(r["result"]) == {"A", "B", "C", "D"}

def test_dijkstra():
    g = {"A": [("B", 1), ("C", 4)], "B": [("C", 2), ("D", 5)], "C": [("D", 1)], "D": []}
    r = dijkstra(g, "A")
    assert r["result"] == {"A": 0, "B": 1, "C": 3, "D": 4}

# ─── DP ───────────────────────────────────────────────────────────────────────

def test_fibonacci():
    assert fibonacci(0)["result"] == 0
    assert fibonacci(1)["result"] == 1
    assert fibonacci(10)["result"] == 55

def test_fibonacci_steps():
    r = fibonacci(5)
    assert r["steps"] == [0, 1, 1, 2, 3, 5]

def test_fibonacci_negative():
    with pytest.raises(ValueError):
        fibonacci(-1)

def test_knapsack():
    r = knapsack([1, 3, 4, 5], [1, 4, 5, 7], 7)
    assert r["result"] == 9

def test_lcs():
    r = lcs("ABCBDAB", "BDCAB")
    assert r["result"] == 4

# ─── Data Structures ──────────────────────────────────────────────────────────

def test_stack():
    s = Stack()
    assert s.is_empty()
    s.push(1); s.push(2); s.push(3)
    assert s.peek() == 3
    assert s.pop() == 3
    assert s.size() == 2

def test_queue():
    q = Queue()
    q.enqueue("a"); q.enqueue("b"); q.enqueue("c")
    assert q.dequeue() == "a"
    assert q.peek() == "b"
    assert q.size() == 2

def test_linked_list():
    ll = LinkedList()
    ll.append(1); ll.append(2); ll.append(3)
    assert ll.to_list() == [1, 2, 3]
    ll.delete(2)
    assert ll.to_list() == [1, 3]
    assert ll.search(3) is True
    assert ll.search(2) is False

def test_bst():
    tree = BST()
    for v in [5, 3, 7, 1, 4, 6, 8]:
        tree.insert(v)
    assert tree.inorder() == [1, 3, 4, 5, 6, 7, 8]
    assert tree.search(4) is True
    assert tree.search(99) is False
    tree.delete(3)
    assert 3 not in tree.inorder()
