import pytest
from textual.app import App
from textual.widgets import DataTable, OptionList

from idlemaxx.cli import (
    ActionLimitScreen,
    ActivityCategoryScreen,
    ActivityScreen,
    CharacterScreen,
    IdlemaxxApp,
    QueueScreen,
    TableViewScreen,
)
from idlemaxx.game import Game


class _TestApp(IdlemaxxApp):
    """IdlemaxxApp backed by an in-memory SQLite DB."""

    def __init__(self) -> None:
        App.__init__(self)
        self.game = Game("sqlite:///:memory:")


@pytest.fixture
def app() -> _TestApp:
    return _TestApp()


@pytest.fixture
def app_with_character() -> _TestApp:
    a = _TestApp()
    a.game.create_character("TestChar")
    return a


def _menu_ids(app: _TestApp) -> list[str | None]:
    return [o.id for o in app.query_one("#menu", OptionList)._options]


async def test_main_menu_has_create_and_quit(app: _TestApp) -> None:
    async with app.run_test():
        ids = _menu_ids(app)
        assert "create_character" in ids
        assert "quit" in ids


async def test_create_character(app: _TestApp) -> None:
    async with app.run_test() as pilot:
        await pilot.press("enter")  # "Create a new character" is index 0
        await pilot.pause(0.001)
        for char in "Hero":
            await pilot.press(char)
        await pilot.press("enter")
        await pilot.pause(0.001)
        assert "char:Hero" in _menu_ids(app)


async def test_select_character_opens_character_screen(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # "TestChar" is index 0
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, CharacterScreen)


async def test_character_screen_back(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("down", "enter")  # select TestChar
        await pilot.pause(0.001)
        await pilot.press("down", "down", "down", "down", "down", "down", "enter")  # "Back" is index 5
        await pilot.pause(0.001)
        assert not isinstance(app_with_character.screen, CharacterScreen)


async def test_view_skills(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # select character (main menu starts at index 0)
        await pilot.pause(0.001)
        await pilot.press("down", "enter")  # "View skills" is index 1 (CharacterScreen starts at 0)
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, TableViewScreen)
        await pilot.press("escape")
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, CharacterScreen)


async def test_view_items(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # select character (main menu starts at index 0)
        await pilot.pause(0.001)
        await pilot.press("down", "down", "enter")  # "View items" is index 2
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, TableViewScreen)
        await pilot.press("escape")
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, CharacterScreen)


async def test_start_activity(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # select character (main menu starts at index 0)
        await pilot.pause(0.001)
        await pilot.press("enter")  # "Start activity" is index 0 (CharacterScreen starts at 0)
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, ActivityCategoryScreen)
        await pilot.press("enter")  # select first category
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, ActivityScreen)
        await pilot.press("enter")  # select first activity row → ActionLimitScreen
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, ActionLimitScreen)
        await pilot.press("tab")  # move focus from Input to OptionList
        await pilot.pause(0.001)
        await pilot.press("enter")  # select "▶ Start" (index 0)
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, CharacterScreen)
        app_with_character.game._session.refresh(app_with_character.character)
        assert app_with_character.character.current_activity is not None


async def test_delete_character_confirmed(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # select character (main menu starts at index 0)
        await pilot.pause(0.001)
        await pilot.press("down", "down", "down", "down", "down", "enter")  # "Delete character" is index 5
        await pilot.pause(0.001)
        await pilot.press("enter")  # "Yes" is index 0 (ConfirmScreen starts at 0)
        await pilot.pause(0.001)
        assert "char:TestChar" not in _menu_ids(app_with_character)


async def test_delete_character_cancelled(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # select character (main menu starts at index 0)
        await pilot.pause(0.001)
        await pilot.press("down", "down", "down", "down", "down", "enter")  # "Delete character" is index 5
        await pilot.pause(0.001)
        await pilot.press("down", "enter")  # "No" is index 1 (start at 0, down → 1)
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, CharacterScreen)


# Wrapping tests


async def test_option_list_wraps_down_to_first(app_with_character: _TestApp) -> None:
    """Down on the last option wraps to index 0, not index 1."""
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # open CharacterScreen
        await pilot.pause(0.001)
        ol = app_with_character.screen.query_one(OptionList)
        await pilot.press("down", "down", "down", "down", "down", "down")  # 0 → 6 ("Back")
        await pilot.pause(0.001)
        assert ol.highlighted == 6
        await pilot.press("down")  # should wrap to 0, not 1
        await pilot.pause(0.001)
        assert ol.highlighted == 0


async def test_option_list_wraps_up_to_last(app_with_character: _TestApp) -> None:
    """Up on the first option wraps to the last index, not second-to-last."""
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # open CharacterScreen
        await pilot.pause(0.001)
        ol = app_with_character.screen.query_one(OptionList)
        assert ol.highlighted == 0
        await pilot.press("up")  # should wrap to 6 ("Back"), not 5
        await pilot.pause(0.001)
        assert ol.highlighted == 6


async def test_data_table_wraps_down_to_first(app_with_character: _TestApp) -> None:
    """Down on the last row wraps to row 0, not row 1."""
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # open CharacterScreen
        await pilot.pause(0.001)
        await pilot.press("enter")  # open ActivityCategoryScreen ("Start activity" is index 0)
        await pilot.pause(0.001)
        await pilot.press("enter")  # select first category → open ActivityScreen
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, ActivityScreen)
        table = app_with_character.screen.query_one(DataTable)
        last_row = table.row_count - 1
        table.move_cursor(row=last_row)
        await pilot.pause(0.001)
        assert table.cursor_row == last_row
        await pilot.press("down")  # should wrap to row 0, not row 1
        await pilot.pause(0.001)
        assert table.cursor_row == 0


async def test_data_table_wraps_up_to_last(app_with_character: _TestApp) -> None:
    """Up on the first row wraps to the last row, not second-to-last."""
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # open CharacterScreen
        await pilot.pause(0.001)
        await pilot.press("enter")  # open ActivityCategoryScreen
        await pilot.pause(0.001)
        await pilot.press("enter")  # select first category → open ActivityScreen
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, ActivityScreen)
        table = app_with_character.screen.query_one(DataTable)
        assert table.cursor_row == 0
        last_row = table.row_count - 1
        await pilot.press("up")  # should wrap to last row, not second-to-last
        await pilot.pause(0.001)
        assert table.cursor_row == last_row


async def test_view_queue(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # select character
        await pilot.pause(0.001)
        await pilot.press("down", "down", "down", "enter")  # "View queue" is index 3
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, QueueScreen)
        await pilot.press("escape")
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, CharacterScreen)


async def test_action_limit_screen_cancel(app_with_character: _TestApp) -> None:
    async with app_with_character.run_test() as pilot:
        await pilot.press("enter")  # select character
        await pilot.pause(0.001)
        await pilot.press("enter")  # "Start activity" is index 0
        await pilot.pause(0.001)
        await pilot.press("enter")  # select first category
        await pilot.pause(0.001)
        await pilot.press("enter")  # select first activity → ActionLimitScreen
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, ActionLimitScreen)
        await pilot.press("escape")  # cancel
        await pilot.pause(0.001)
        assert isinstance(app_with_character.screen, ActivityScreen)
        app_with_character.game._session.refresh(app_with_character.character)
        assert app_with_character.character.current_activity is None
