import importlib
from types import SimpleNamespace
from typing import Any

import pytest
import typer
from pytest import MonkeyPatch
from typer.testing import CliRunner


def test_tui_command_returns_dependency_error_when_textual_missing(
    monkeypatch: MonkeyPatch,
    tui_base_modules: SimpleNamespace,
) -> None:
    runner = CliRunner()
    monkeypatch.chdir(tui_base_modules.BACKEND_DIR)

    def raise_missing_textual(module_name: str) -> Any:
        if module_name == 'cli.tui.app':
            raise ModuleNotFoundError("No module named 'textual'", name='textual')
        return importlib.import_module(module_name)

    monkeypatch.setattr(tui_base_modules.cli_tui_commands, 'import_module', raise_missing_textual)
    isolated_cli = typer.Typer()
    tui_base_modules.cli_tui_commands.TUI_COMMAND_REGISTRATION.register(isolated_cli)

    result = runner.invoke(isolated_cli, ['tui', '--env=dev'])

    assert result.exit_code == tui_base_modules.DEPENDENCY_ERROR
    assert '当前环境未安装 TUI 可选依赖' in result.stdout
    assert 'pip install -r requirements.txt' in result.stdout


def test_tui_app_mount_pushes_first_workspace_screen(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    recorded_calls: list[tuple[str, object]] = []

    class FakeDashboardScreen:
        def __init__(
            self,
            snapshot: object,
            env: str,
            active_view: str,
            navigation_items: list[object],
            refreshed_at: str,
        ) -> None:
            self.snapshot = snapshot
            self.env = env
            self.active_view = active_view
            self.navigation_items = navigation_items
            self.refreshed_at = refreshed_at

    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'dashboard',
        lambda env: tui_modules.cli_tui_app.DashboardSnapshot(env=env, panels=[]),
    )
    monkeypatch.setattr(tui_modules.cli_tui_app, 'DashboardScreen', FakeDashboardScreen)

    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    monkeypatch.setattr(app, 'push_screen', lambda screen: recorded_calls.append(('push', screen)))
    monkeypatch.setattr(app, 'switch_screen', lambda screen: recorded_calls.append(('switch', screen)))

    app.on_mount()

    assert recorded_calls
    assert recorded_calls[0][0] == 'push'
    assert isinstance(recorded_calls[0][1], FakeDashboardScreen)
    assert recorded_calls[0][1].env == 'dev'
    assert recorded_calls[0][1].active_view == 'dashboard'
    assert recorded_calls[0][1].navigation_items
    assert recorded_calls[0][1].refreshed_at
    assert app.current_view == 'dashboard'
    assert app.screen_navigator.initialized is True


def test_open_view_switches_to_expected_action(tui_modules: SimpleNamespace) -> None:
    calls: list[str] = []
    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')

    def fake_show_view(view_key: str) -> None:
        calls.append(tui_modules.cli_tui_app.TUI_VIEW_REGISTRY.resolve_view_key(view_key))

    app.show_view = fake_show_view  # type: ignore[method-assign]

    app.open_view('jobs')
    app.open_view('app')
    app.open_view('ops')
    app.open_view('crypto')
    app.open_view('unknown')

    assert calls == ['jobs', 'app', 'ops', 'crypto', 'dashboard']


def test_workspace_header_render_includes_particle_line(tui_modules: SimpleNamespace) -> None:
    header = tui_modules.cli_tui_widgets.WorkspaceHeader('dev', 'dashboard')

    rendered = header.render()
    rendered_text = rendered.plain

    assert rendered.__class__.__name__ == 'Text'
    assert 'ShiErFeng 控制台' in rendered_text
    assert '环境 DEV · 页面 总览' in rendered_text
    assert any(symbol in rendered_text for symbol in ('█', '▓', '▒', '░', '·', '─'))


def test_workspace_header_centers_title_using_runtime_width(tui_modules: SimpleNamespace) -> None:
    header = tui_modules.cli_tui_widgets.WorkspaceHeader('dev', 'dashboard')
    header.styles.width = 140

    first_line = header.render().plain.splitlines()[0]

    assert first_line.startswith(' ')
    assert 'ShiErFeng 控制台' in first_line


def test_workspace_header_keeps_title_alignment_stable_across_frames(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    header = tui_modules.cli_tui_widgets.WorkspaceHeader('dev', 'dashboard')
    monotonic_values = iter([10.0, 10.0, 11.0, 11.0])
    workspace_module = importlib.import_module('cli.tui.widgets.workspace')
    monkeypatch.setattr(workspace_module, 'monotonic', lambda: next(monotonic_values))

    first_line_a = header.render().plain.splitlines()[0]
    first_line_b = header.render().plain.splitlines()[0]

    assert first_line_a.index('ShiErFeng 控制台') == first_line_b.index('ShiErFeng 控制台')


def test_workspace_hero_pulses_border_with_expected_palette(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    hero = tui_modules.cli_tui_widgets.WorkspaceHero(
        title='标题',
        subtitle='副标题',
        env='dev',
        active_view='dashboard',
        summary='摘要',
        refreshed_at='2026-05-12 12:00:00',
    )
    workspace_module = importlib.import_module('cli.tui.widgets.workspace')
    monkeypatch.setattr(workspace_module, 'monotonic', lambda: 10.0)
    original_border_text = str(hero.styles.border)

    hero._pulse_border()

    border_text = str(hero.styles.border)

    assert 'double' in border_text
    assert border_text != original_border_text


def test_workspace_hero_render_uses_rich_text_for_title_glow(tui_modules: SimpleNamespace) -> None:
    hero = tui_modules.cli_tui_widgets.WorkspaceHero(
        title='标题',
        subtitle='副标题',
        env='dev',
        active_view='dashboard',
        summary='摘要',
        refreshed_at='2026-05-12 12:00:00',
    )

    rendered = hero._build_render_text()

    assert rendered.__class__.__name__ == 'Text'
    assert '标题' in rendered.plain
    assert '副标题' in rendered.plain
    assert '运行摘要 · 摘要' in rendered.plain


def test_tui_app_runner_runs_created_application(tui_modules: SimpleNamespace) -> None:
    recorded_envs: list[str] = []
    recorded_runs: list[str] = []

    class FakeApp:
        def __init__(self, env: str) -> None:
            recorded_envs.append(env)

        def run(self) -> None:
            recorded_runs.append('run')

    runner = tui_modules.cli_tui_app.TuiAppRunner(FakeApp)

    runner.run('dev')

    assert recorded_envs == ['dev']
    assert recorded_runs == ['run']


def test_tui_app_show_jobs_uses_remembered_filter(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    recorded_filter_keys: list[tuple[str, str]] = []
    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    app.remember_browser_filter('jobs', 'failed')
    app.remember_browser_query('jobs', 'sync')
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'jobs',
        lambda env, filter_key='all', query='': (
            recorded_filter_keys.append((filter_key, query))
            or tui_modules.cli_tui_app.BrowserPageSnapshot(
                title='任务',
                subtitle='subtitle',
                records=[],
                shared_sections=[],
                filters=[],
                active_filter_key=filter_key,
                search=None,
            )
        ),
    )
    monkeypatch.setattr(app.screen_navigator, 'show', lambda screen: None)
    app.action_show_jobs()

    assert recorded_filter_keys == [('failed', 'sync')]


def test_tui_app_show_configs_uses_remembered_filter(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    recorded_filter_keys: list[tuple[str, str]] = []
    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    app.remember_browser_filter('configs', 'cache-drift')
    app.remember_browser_query('configs', 'site')
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'configs',
        lambda env, filter_key='all', query='': (
            recorded_filter_keys.append((filter_key, query))
            or tui_modules.cli_tui_app.BrowserPageSnapshot(
                title='参数配置',
                subtitle='subtitle',
                records=[],
                shared_sections=[],
                filters=[],
                active_filter_key=filter_key,
                search=None,
            )
        ),
    )
    monkeypatch.setattr(app.screen_navigator, 'show', lambda screen: None)
    app.action_show_configs()

    assert recorded_filter_keys == [('cache-drift', 'site')]


def test_tui_app_show_cache_and_gen_use_remembered_query(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    cache_queries: list[str] = []
    gen_queries: list[str] = []
    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    app.remember_browser_query('cache', 'sys')
    app.remember_browser_query('gen', 'user')
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'cache',
        lambda env, query='': (
            cache_queries.append(query)
            or tui_modules.cli_tui_app.BrowserPageSnapshot(
                title='缓存',
                subtitle='subtitle',
                records=[],
                shared_sections=[],
                filters=[],
                active_filter_key=None,
                search=None,
            )
        ),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'gen',
        lambda env, query='': (
            gen_queries.append(query)
            or tui_modules.cli_tui_app.BrowserPageSnapshot(
                title='代码生成',
                subtitle='subtitle',
                records=[],
                shared_sections=[],
                filters=[],
                active_filter_key=None,
                search=None,
            )
        ),
    )
    monkeypatch.setattr(app.screen_navigator, 'show', lambda screen: None)
    app.action_show_cache()
    app.action_show_gen()

    assert cache_queries == ['sys']
    assert gen_queries == ['user']


def test_tui_app_detail_views_use_remembered_query(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    database_queries: list[str] = []
    app_queries: list[str] = []
    ops_queries: list[str] = []
    crypto_queries: list[str] = []
    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    app.remember_browser_query('database', 'head')
    app.remember_browser_query('app', 'route')
    app.remember_browser_query('ops', 'disk')
    app.remember_browser_query('crypto', 'kid')
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'database',
        lambda env, query='': (
            database_queries.append(query)
            or tui_modules.cli_tui_app.DetailPageSnapshot(title='数据库', subtitle='subtitle', sections=[])
        ),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'app',
        lambda env, query='': (
            app_queries.append(query)
            or tui_modules.cli_tui_app.DetailPageSnapshot(title='应用', subtitle='subtitle', sections=[])
        ),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'ops',
        lambda env, query='': (
            ops_queries.append(query)
            or tui_modules.cli_tui_app.DetailPageSnapshot(title='运维', subtitle='subtitle', sections=[])
        ),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'crypto',
        lambda env, query='': (
            crypto_queries.append(query)
            or tui_modules.cli_tui_app.DetailPageSnapshot(title='传输加密', subtitle='subtitle', sections=[])
        ),
    )
    monkeypatch.setattr(app.screen_navigator, 'show', lambda screen: None)
    app.action_show_database()
    app.action_show_app()
    app.action_show_ops()
    app.action_show_crypto()

    assert database_queries == ['head']
    assert app_queries == ['route']
    assert ops_queries == ['disk']
    assert crypto_queries == ['kid']


@pytest.mark.asyncio
async def test_tui_app_open_view_updates_screen_across_multiple_switches(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'dashboard',
        lambda env: tui_modules.cli_tui_app.DashboardSnapshot(env=env, panels=[]),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'app',
        lambda env, query='': tui_modules.cli_tui_app.DetailPageSnapshot(
            title='应用详情',
            subtitle=query or 'app-subtitle',
            sections=[
                tui_modules.cli_tui_adapters.DetailSectionSnapshot(
                    title='应用分区',
                    status='ok',
                    lines=['app-line'],
                )
            ],
        ),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'ops',
        lambda env, query='': tui_modules.cli_tui_app.DetailPageSnapshot(
            title='运维详情',
            subtitle=query or 'ops-subtitle',
            sections=[
                tui_modules.cli_tui_adapters.DetailSectionSnapshot(
                    title='运维分区',
                    status='warn',
                    lines=['ops-line'],
                )
            ],
        ),
    )

    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    async with app.run_test(size=(120, 40)) as pilot:
        await pilot.pause()
        await pilot.pause()

        assert type(app.screen).__name__ == 'DashboardScreen'
        assert app.current_view == 'dashboard'

        app.open_view('app')
        await pilot.pause()
        await pilot.pause()

        assert type(app.screen).__name__ == 'DetailScreen'
        assert app.current_view == 'app'
        assert app.screen.snapshot.title == '应用详情'

        app.open_view('ops')
        await pilot.pause()
        await pilot.pause()

        assert type(app.screen).__name__ == 'DetailScreen'
        assert app.current_view == 'ops'
        assert app.screen.snapshot.title == '运维详情'


@pytest.mark.asyncio
async def test_tui_sidebar_highlight_updates_screen_across_multiple_switches(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'dashboard',
        lambda env: tui_modules.cli_tui_app.DashboardSnapshot(env=env, panels=[]),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'app',
        lambda env, query='': tui_modules.cli_tui_app.DetailPageSnapshot(
            title='应用详情',
            subtitle='app-subtitle',
            sections=[
                tui_modules.cli_tui_adapters.DetailSectionSnapshot(
                    title='应用分区',
                    status='ok',
                    lines=['app-line'],
                )
            ],
        ),
    )
    monkeypatch.setitem(
        tui_modules.cli_tui_app.TUI_SNAPSHOT_COLLECTOR_REGISTRY.collectors,
        'ops',
        lambda env, query='': tui_modules.cli_tui_app.DetailPageSnapshot(
            title='运维详情',
            subtitle='ops-subtitle',
            sections=[
                tui_modules.cli_tui_adapters.DetailSectionSnapshot(
                    title='运维分区',
                    status='warn',
                    lines=['ops-line'],
                )
            ],
        ),
    )

    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    async with app.run_test(size=(120, 40)) as pilot:
        await pilot.pause()
        await pilot.pause()

        assert type(app.screen).__name__ == 'DashboardScreen'
        assert app.current_view == 'dashboard'

        await pilot.press('down')
        await pilot.pause()
        await pilot.pause()

        assert type(app.screen).__name__ == 'DetailScreen'
        assert app.current_view == 'app'
        assert app.screen.snapshot.title == '应用详情'

        await pilot.press('down')
        await pilot.pause()
        await pilot.pause()

        assert type(app.screen).__name__ == 'DetailScreen'
        assert app.current_view == 'ops'
        assert app.screen.snapshot.title == '运维详情'
