"""
Tests for the core framework: keybindings, navigation, and screen base class.
"""

import pytest

from console.core.keybindings import Key, KeyBindings
from console.core.navigation import Navigation
from console.core.screens import Screen


# =========================================================================
# Key constants
# =========================================================================

class TestKeyConstants:
    def test_escape_is_27(self):
        assert Key.ESCAPE == 27

    def test_enter_is_10(self):
        assert Key.ENTER == 10

    def test_tab_is_9(self):
        assert Key.TAB == 9

    def test_backspace_is_127(self):
        assert Key.BACKSPACE == 127

    def test_arrow_keys_are_not_none(self):
        assert Key.UP is not None
        assert Key.DOWN is not None
        assert Key.LEFT is not None
        assert Key.RIGHT is not None

    def test_page_keys_are_not_none(self):
        assert Key.PGUP is not None
        assert Key.PGDN is not None

    def test_home_end_are_not_none(self):
        assert Key.HOME is not None
        assert Key.END is not None

    def test_f1_is_not_none(self):
        assert Key.F1 is not None

    def test_f10_is_not_none(self):
        assert Key.F10 is not None

    def test_f_keys_are_sequential(self):
        """F1 through F10 should be sequential curses key codes."""
        for i in range(1, 10):
            f_current = getattr(Key, f"F{i}")
            f_next = getattr(Key, f"F{i + 1}")
            assert f_next == f_current + 1


# =========================================================================
# KeyBindings
# =========================================================================

class TestKeyBindings:
    def test_bind_and_handle_calls_callback(self):
        kb = KeyBindings()
        called = []
        kb.bind(Key.ENTER, lambda: called.append(True))
        result = kb.handle(Key.ENTER)
        assert result is True
        assert len(called) == 1

    def test_handle_returns_false_for_unbound_key(self):
        kb = KeyBindings()
        result = kb.handle(Key.ESCAPE)
        assert result is False

    def test_bind_overwrites_previous(self):
        kb = KeyBindings()
        called_a = []
        called_b = []
        kb.bind(Key.ENTER, lambda: called_a.append(True))
        kb.bind(Key.ENTER, lambda: called_b.append(True))
        kb.handle(Key.ENTER)
        assert len(called_a) == 0
        assert len(called_b) == 1

    def test_multiple_bindings(self):
        kb = KeyBindings()
        results = {}
        kb.bind(Key.ENTER, lambda: results.update(enter=True))
        kb.bind(Key.ESCAPE, lambda: results.update(escape=True))
        kb.handle(Key.ENTER)
        kb.handle(Key.ESCAPE)
        assert results == {"enter": True, "escape": True}

    def test_set_footer_and_get_footer_labels(self):
        kb = KeyBindings()
        labels = [("F1", "Help"), ("F10", "Quit")]
        kb.set_footer(labels)
        assert kb.get_footer_labels() == labels

    def test_get_footer_labels_empty_by_default(self):
        kb = KeyBindings()
        assert kb.get_footer_labels() == []


# =========================================================================
# Navigation
# =========================================================================

class TestNavigation:
    def test_initial_state_is_empty(self):
        nav = Navigation()
        assert nav.current() is None
        assert nav.depth() == 0

    def test_push_and_current(self):
        nav = Navigation()
        nav.push("brands", context={"page": 1}, label="Brands")
        result = nav.current()
        assert result is not None
        screen_name, context = result
        assert screen_name == "brands"
        assert context == {"page": 1}

    def test_push_increases_depth(self):
        nav = Navigation()
        nav.push("brands", label="Brands")
        assert nav.depth() == 1
        nav.push("models", label="Models")
        assert nav.depth() == 2

    def test_pop_returns_previous_screen(self):
        nav = Navigation()
        nav.push("brands", context={"page": 1}, label="Brands")
        nav.push("models", context={"brand": "TOYOTA"}, label="Models")
        popped = nav.pop()
        assert popped is not None
        screen_name, context = popped
        assert screen_name == "models"
        assert context == {"brand": "TOYOTA"}
        # Current should now be brands
        current = nav.current()
        assert current[0] == "brands"

    def test_pop_on_empty_returns_none(self):
        nav = Navigation()
        result = nav.pop()
        assert result is None

    def test_pop_on_single_item_returns_it_and_empties(self):
        nav = Navigation()
        nav.push("home", label="Home")
        popped = nav.pop()
        assert popped is not None
        assert popped[0] == "home"
        assert nav.current() is None
        assert nav.depth() == 0

    def test_breadcrumb_returns_label_list(self):
        nav = Navigation()
        nav.push("brands", label="Brands")
        nav.push("models", label="Toyota Models")
        nav.push("years", label="2020")
        assert nav.breadcrumb() == ["Brands", "Toyota Models", "2020"]

    def test_breadcrumb_empty_when_no_items(self):
        nav = Navigation()
        assert nav.breadcrumb() == []

    def test_breadcrumb_uses_screen_name_as_fallback(self):
        nav = Navigation()
        nav.push("brands")
        assert nav.breadcrumb() == ["brands"]

    def test_clear_empties_stack(self):
        nav = Navigation()
        nav.push("brands", label="Brands")
        nav.push("models", label="Models")
        nav.clear()
        assert nav.depth() == 0
        assert nav.current() is None
        assert nav.breadcrumb() == []

    def test_context_defaults_to_none(self):
        nav = Navigation()
        nav.push("home", label="Home")
        screen_name, context = nav.current()
        assert context is None


# =========================================================================
# Screen base class
# =========================================================================

class TestScreen:
    def test_has_name_and_title(self):
        screen = Screen("brands", "Select Brand")
        assert screen.name == "brands"
        assert screen.title == "Select Brand"

    def test_on_enter_is_callable(self):
        screen = Screen("test", "Test")
        # Should not raise
        screen.on_enter(context=None, db=None, renderer=None)

    def test_on_key_is_callable(self):
        screen = Screen("test", "Test")
        # Should not raise, returns None by default
        result = screen.on_key(key=10, context=None, db=None, renderer=None, nav=None)
        assert result is None

    def test_render_is_callable(self):
        screen = Screen("test", "Test")
        # Should not raise
        screen.render(context=None, db=None, renderer=None)
