"""Tests for the SkilarkApp shell: launch, tab navigation, keybindings."""

import pytest
from unittest.mock import MagicMock

from skilark_cli.tui.app import SkilarkApp, HeaderBar, HelpScreen


@pytest.fixture
def app():
    return SkilarkApp(client=None)


class TestAppLaunch:
    async def test_app_starts(self, app):
        async with app.run_test() as pilot:
            assert app.is_running

    async def test_header_bar_present(self, app):
        async with app.run_test() as pilot:
            header = app.query_one(HeaderBar)
            assert header is not None

    async def test_header_bar_has_id(self, app):
        async with app.run_test() as pilot:
            header = app.query_one("#header-bar")
            assert header is not None

    async def test_default_tab_is_drill(self, app):
        async with app.run_test() as pilot:
            assert app._active_tab == 0


class TestTabNavigation:
    async def test_switch_to_drill(self, app):
        async with app.run_test() as pilot:
            await pilot.press("1")
            assert app._active_tab == 0

    async def test_switch_to_signals(self, app):
        async with app.run_test() as pilot:
            await pilot.press("2")
            assert app._active_tab == 1

    async def test_switch_to_jobs(self, app):
        async with app.run_test() as pilot:
            await pilot.press("3")
            assert app._active_tab == 2

    async def test_switch_to_profile(self, app):
        async with app.run_test() as pilot:
            await pilot.press("4")
            assert app._active_tab == 3

    async def test_same_tab_noop(self, app):
        """Pressing the current tab key doesn't remount."""
        async with app.run_test() as pilot:
            await pilot.press("1")  # already on drill
            assert app._active_tab == 0  # unchanged


class TestHelpScreen:
    async def test_help_opens_modal(self, app):
        async with app.run_test() as pilot:
            await pilot.press("question_mark")
            assert isinstance(app.screen, HelpScreen)

    async def test_help_closes_with_question_mark(self, app):
        async with app.run_test() as pilot:
            await pilot.press("question_mark")
            assert isinstance(app.screen, HelpScreen)
            await pilot.press("question_mark")
            assert not isinstance(app.screen, HelpScreen)

    async def test_help_closes_with_escape(self, app):
        async with app.run_test() as pilot:
            await pilot.press("question_mark")
            assert isinstance(app.screen, HelpScreen)
            await pilot.press("escape")
            assert not isinstance(app.screen, HelpScreen)

    async def test_help_blocks_tab_switch(self, app):
        """Number keys while help is open should not switch tabs."""
        async with app.run_test() as pilot:
            await pilot.press("question_mark")
            await pilot.press("3")
            # Tab should not have changed
            assert app._active_tab == 0
            # Modal should still be open (keys are blocked, not dismissed)
            assert isinstance(app.screen, HelpScreen)

    async def test_help_blocks_quit(self, app):
        """q while help is open should not quit the app."""
        async with app.run_test() as pilot:
            await pilot.press("question_mark")
            await pilot.press("q")
            assert isinstance(app.screen, HelpScreen)
            assert app.is_running


class TestAppConstructor:
    def test_app_stores_topics(self):
        """AC15: SkilarkApp stores topics for DrillPane to use."""
        app = SkilarkApp(topics=["python", "dsa"])
        assert app.topics == ["python", "dsa"]

    def test_app_stores_coding_language(self):
        """SkilarkApp stores coding_language for future Drill coding phase."""
        app = SkilarkApp(coding_language="python")
        assert app.coding_language == "python"

    def test_app_defaults_topics_to_empty(self):
        app = SkilarkApp()
        assert app.topics == []

    def test_app_defaults_coding_language_to_none(self):
        app = SkilarkApp()
        assert app.coding_language is None


class TestThemeCycle:
    async def test_default_theme_is_dark(self, app):
        async with app.run_test():
            assert app.theme == "textual-dark"

    async def test_cycle_advances_to_next_theme(self, app):
        async with app.run_test() as pilot:
            themes = sorted(app.available_themes)
            idx = themes.index("textual-dark")
            expected = themes[(idx + 1) % len(themes)]
            await pilot.press("t")
            assert app.theme == expected

    async def test_cycle_wraps_around(self, app):
        async with app.run_test() as pilot:
            themes = sorted(app.available_themes)
            for _ in range(len(themes)):
                await pilot.press("t")
            assert app.theme == "textual-dark"  # back to start

    async def test_theme_blocked_during_help(self, app):
        async with app.run_test() as pilot:
            await pilot.press("question_mark")
            assert isinstance(app.screen, HelpScreen)
            await pilot.press("t")
            assert app.theme == "textual-dark"  # unchanged

    def test_legacy_light_theme_from_config(self):
        app = SkilarkApp(theme="light")
        assert app.theme == "textual-light"

    def test_named_theme_from_config(self):
        app = SkilarkApp(theme="dracula")
        assert app.theme == "dracula"

    async def test_cycle_persists_to_config_store(self, tmp_path):
        from skilark_cli.config_store import ConfigStore

        store = ConfigStore(config_dir=tmp_path)
        store.save(user_id="abc", topics=[])
        app = SkilarkApp(config_store=store)
        async with app.run_test() as pilot:
            await pilot.press("t")
        config = store.load()
        assert config["theme"] == app.theme

    async def test_direct_theme_set_persists(self, tmp_path):
        """Setting app.theme directly (as palette does) also persists."""
        from skilark_cli.config_store import ConfigStore

        store = ConfigStore(config_dir=tmp_path)
        store.save(user_id="abc", topics=[])
        app = SkilarkApp(config_store=store)
        async with app.run_test():
            app.theme = "dracula"
        config = store.load()
        assert config["theme"] == "dracula"


class TestQuit:
    async def test_single_q_does_not_quit(self, app):
        async with app.run_test() as pilot:
            await pilot.press("q")
            assert app.is_running

    async def test_double_q_quits(self, app):
        async with app.run_test() as pilot:
            await pilot.press("q")
            await pilot.press("q")
        assert not app.is_running

    async def test_q_after_timeout_resets(self, app):
        app._quit_timeout = 0.1  # shorten for test
        async with app.run_test() as pilot:
            await pilot.press("q")
            import asyncio
            await asyncio.sleep(0.15)
            await pilot.press("q")
            assert app.is_running  # reset, need another q

    async def test_quit_blocked_during_help(self, app):
        async with app.run_test() as pilot:
            await pilot.press("question_mark")
            await pilot.press("q")
            await pilot.press("q")
            assert app.is_running  # blocked by help modal


class TestTabStatePreservation:
    async def test_panes_mounted_once(self):
        """All 4 panes are mounted at startup."""
        app = SkilarkApp(client=None)
        async with app.run_test() as pilot:
            assert app.query_one("#drill-pane") is not None
            assert app.query_one("#signals-pane") is not None
            assert app.query_one("#jobs-pane") is not None
            assert app.query_one("#profile-pane") is not None

    async def test_only_active_pane_visible(self):
        """Only the active tab's pane has display=True."""
        app = SkilarkApp(client=None)
        async with app.run_test() as pilot:
            # Default is Drill (tab 0)
            assert app.query_one("#drill-pane").display is True
            assert app.query_one("#signals-pane").display is False
            assert app.query_one("#jobs-pane").display is False
            assert app.query_one("#profile-pane").display is False

    async def test_switch_tab_toggles_visibility(self):
        """Switching tabs shows the new pane and hides the old one."""
        app = SkilarkApp(client=None)
        async with app.run_test() as pilot:
            await pilot.press("2")  # Signals
            assert app.query_one("#drill-pane").display is False
            assert app.query_one("#signals-pane").display is True

    async def test_pane_not_recreated_on_switch_back(self):
        """Switching back to a tab reuses the same pane instance."""
        app = SkilarkApp(client=None)
        async with app.run_test() as pilot:
            drill_pane = app.query_one("#drill-pane")
            await pilot.press("2")
            await pilot.press("1")
            assert app.query_one("#drill-pane") is drill_pane  # same instance


class TestSessionRestore:
    async def test_restores_last_tab(self, tmp_path):
        """App opens on the last active tab from config."""
        from skilark_cli.config_store import ConfigStore

        store = ConfigStore(config_dir=tmp_path)
        store.save(user_id="u1", topics=[])
        store.save_last_tab(2)
        app = SkilarkApp(client=None, config_store=store)
        async with app.run_test() as pilot:
            assert app._active_tab == 2
            assert app.query_one("#jobs-pane").display is True
            assert app.query_one("#drill-pane").display is False

    async def test_saves_last_tab_on_exit(self, tmp_path):
        """App saves current tab to config on exit."""
        from skilark_cli.config_store import ConfigStore

        store = ConfigStore(config_dir=tmp_path)
        store.save(user_id="u1", topics=[])
        app = SkilarkApp(client=None, config_store=store)
        async with app.run_test() as pilot:
            await pilot.press("3")  # Jobs
        config = store.load()
        assert config.get("last_tab") == 2  # 0-indexed


class TestRapidTabSwitching:
    async def test_rapid_tab_switch_no_crash(self):
        """Switching tabs rapidly should not crash."""
        app = SkilarkApp(client=None)
        async with app.run_test() as pilot:
            for key in ["1", "2", "3", "4", "1", "2", "3", "4"]:
                await pilot.press(key)
            await pilot.pause()
            assert app.query_one("#header-bar") is not None

    async def test_tab_switch_during_loading(self):
        """Switching away from a tab while it's loading should not crash."""
        import time

        client = MagicMock()
        # Stub Drill tab's client calls so default tab mounts cleanly
        client.get_next_challenge.return_value = None
        client.get_stats.return_value = {"total_completed": 0}

        def slow_signals(*a, **kw):
            time.sleep(0.1)
            return {"signals": [], "week_of": "2026-03-10"}

        client.get_signals.side_effect = slow_signals
        app = SkilarkApp(client=client)
        async with app.run_test() as pilot:
            await pilot.press("2")  # Signals (starts loading)
            await pilot.press("1")  # Immediately switch to Drill
            await pilot.pause()
            assert app.query_one("#header-bar") is not None
