from types import SimpleNamespace
from typing import Any

import pytest
from pytest import MonkeyPatch


def test_browser_screen_builds_sections_from_selected_record(tui_modules: SimpleNamespace) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='Jobs',
        subtitle='subtitle',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='job:1',
                title='Job A',
                status='ok',
                summary='summary a',
                metadata_lines=[],
                detail_sections=[
                    tui_modules.cli_tui_adapters.DetailSectionSnapshot(
                        title='Job Detail',
                        status='ok',
                        lines=['detail a'],
                    )
                ],
            )
        ],
        shared_sections=[
            tui_modules.cli_tui_adapters.DetailSectionSnapshot(title='Recent Logs', status='warn', lines=['log a'])
        ],
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='jobs',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    sections = screen._get_sections_for_record(0)

    assert [section.title for section in sections] == ['Job Detail', 'Recent Logs']


def test_browser_screen_resolves_job_actions(tui_modules: SimpleNamespace) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='Jobs',
        subtitle='subtitle',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='job:1',
                title='Job A',
                status='ok',
                summary='正常 · Cron 0/30 * * * * ?',
                metadata_lines=[],
                detail_sections=[],
            )
        ],
        shared_sections=[],
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='jobs',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    primary_action = screen._resolve_action('primary')
    secondary_action = screen._resolve_action('secondary')
    global_action = screen._resolve_action('global')

    assert primary_action is not None
    assert primary_action.command_args == ('job', 'run-once', '1')
    assert secondary_action is not None
    assert secondary_action.command_args == ('job', 'pause', '1')
    assert global_action is not None
    assert global_action.command_args == ('job', 'sync')


def test_browser_screen_builds_action_panel_with_feedback(tui_modules: SimpleNamespace) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='Jobs',
        subtitle='subtitle',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='job:1',
                title='Job A',
                status='ok',
                summary='正常 · Cron 0/30 * * * * ?',
                metadata_lines=[],
                detail_sections=[],
            )
        ],
        shared_sections=[],
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='jobs',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )
    screen._action_feedback_lines = ['动作名称: 执行一次任务', '结果: 成功']

    lines = screen._build_record_action_lines(snapshot.records[0])

    assert any('动作键 [X] · 执行一次任务' in line for line in lines)
    assert any('浏览操作' in line for line in lines)
    assert any('最近动作反馈' in line for line in lines)
    assert any('结果: 成功' in line for line in lines)


@pytest.mark.asyncio
async def test_browser_screen_execute_external_action_suspends_app(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='代码生成',
        subtitle='subtitle',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='gen:201',
                title='sys_user',
                status='ok',
                summary='生成类 SysUser · 模块 system',
                metadata_lines=[],
                detail_sections=[],
            )
        ],
        shared_sections=[],
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='gen',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )
    action = tui_modules.cli_tui_browser.TUI_ACTION_REGISTRY.resolve_browser_action(
        view_key='gen',
        slot='primary',
        record=snapshot.records[0],
        env='dev',
    )
    assert action is not None

    recorded_suspend: list[str] = []

    class DummySuspend:
        def __enter__(self) -> None:
            recorded_suspend.append('enter')

        def __exit__(
            self,
            exc_type: type[BaseException] | None,
            exc: BaseException | None,
            tb: Any,
        ) -> None:
            del exc_type, exc, tb
            recorded_suspend.append('exit')

    def build_suspend() -> DummySuspend:
        return DummySuspend()

    def refresh_current_view() -> None:
        pass

    fake_app = SimpleNamespace(
        suspend=build_suspend,
        action_refresh_current_view=refresh_current_view,
    )
    monkeypatch.setattr(tui_modules.cli_tui_browser.BrowserScreen, 'app', property(lambda self: fake_app))
    monkeypatch.setattr(
        tui_modules.cli_tui_interactions,
        'TUI_ACTION_EXECUTION_SERVICE',
        SimpleNamespace(
            execute_external=lambda spec: tui_modules.cli_tui_browser.TuiActionResult(
                spec=spec,
                external_exit_code=0,
                external_message='外部交互命令已执行完成',
            ),
            build_result_lines=lambda result: [],
        ),
    )
    monkeypatch.setattr(screen, 'notify', lambda *args, **kwargs: None)

    async def fake_render_record_detail(*, eager: bool = False) -> None:
        del eager

    monkeypatch.setattr(screen, '_render_record_detail', fake_render_record_detail)

    await screen._execute_action(action)

    assert recorded_suspend == ['enter', 'exit']


def test_browser_screen_filter_shortcut_remembers_and_refreshes(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='任务',
        subtitle='subtitle',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='job:1',
                title='Job A',
                status='ok',
                summary='正常 · Cron 0/30 * * * * ?',
                metadata_lines=[],
                detail_sections=[],
            )
        ],
        shared_sections=[],
        filters=list(tui_modules.cli_tui_search.JOB_FILTER_OPTIONS),
        active_filter_key='all',
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='jobs',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    recorded_filters: list[tuple[str, str]] = []
    refresh_calls: list[str] = []
    fake_app = SimpleNamespace(
        remember_browser_filter=lambda view_key, filter_key: recorded_filters.append((view_key, filter_key)),
        action_refresh_current_view=lambda: refresh_calls.append('refresh'),
    )
    monkeypatch.setattr(tui_modules.cli_tui_browser.BrowserScreen, 'app', property(lambda self: fake_app))

    screen.action_apply_filter_2()

    assert recorded_filters == [('jobs', 'failed')]
    assert refresh_calls == ['refresh']


@pytest.mark.asyncio
async def test_browser_screen_detail_loader_failure_falls_back_to_failure_section(
    tui_modules: SimpleNamespace,
) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='任务',
        subtitle='subtitle',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='job:1',
                title='任务一',
                status='warn',
                summary='摘要',
                metadata_lines=[],
                detail_sections=[],
                detail_loader=lambda: (_ for _ in ()).throw(RuntimeError('boom')),
            )
        ],
        shared_sections=[],
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='jobs',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    rendered_calls: list[bool] = []

    async def fake_render_record_detail(*, eager: bool = False) -> None:
        del eager
        rendered_calls.append(True)

    screen._render_record_detail = fake_render_record_detail  # type: ignore[method-assign]
    screen._record_detail_request_id = 1

    await screen._load_record_detail_async(0, 1)

    cached_sections = snapshot.records[0]._cached_detail_sections

    assert rendered_calls == [True]
    assert cached_sections is not None
    assert len(cached_sections) == 1
    assert cached_sections[0].status == 'fail'
    assert cached_sections[0].title == '任务详情加载失败'
    assert any('boom' in line for line in cached_sections[0].lines)


def test_browser_screen_search_submit_remembers_and_refreshes(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='任务',
        subtitle='subtitle',
        records=[],
        shared_sections=[],
        filters=list(tui_modules.cli_tui_search.JOB_FILTER_OPTIONS),
        active_filter_key='all',
        search=tui_modules.cli_tui_search.PageSearchContext(
            placeholder='按任务名搜索',
            query='',
            suggestions=['sync-user'],
        ),
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='jobs',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    recorded_queries: list[tuple[str, str]] = []
    refresh_calls: list[str] = []
    fake_app = SimpleNamespace(
        remember_browser_query=lambda view_key, query: recorded_queries.append((view_key, query)),
        action_refresh_current_view=lambda: refresh_calls.append('refresh'),
    )
    monkeypatch.setattr(tui_modules.cli_tui_browser.BrowserScreen, 'app', property(lambda self: fake_app))

    screen._handle_search_submitted('sync')

    assert recorded_queries == [('jobs', 'sync')]
    assert refresh_calls == ['refresh']


def test_browser_screen_support_builds_summary_actions_and_fallbacks(
    tui_modules: SimpleNamespace,
) -> None:
    support = tui_modules.cli_tui_browser.BrowserScreenSupport()
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='任务',
        subtitle='subtitle',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='job:1',
                title='Job A',
                status='ok',
                summary='正常 · Cron 0/30 * * * * ?',
                metadata_lines=[],
                detail_sections=[],
            )
        ],
        shared_sections=[],
        filters=list(tui_modules.cli_tui_search.JOB_FILTER_OPTIONS),
        active_filter_key='all',
        search=tui_modules.cli_tui_search.PageSearchContext(
            placeholder='按任务名搜索',
            query='sync',
            suggestions=['sync-user'],
        ),
    )

    summary = support.build_summary_text(snapshot, 'jobs')
    filter_bar = support.build_filter_bar_text(snapshot)
    action = support.resolve_action(active_view='jobs', slot='primary', record=snapshot.records[0], env='dev')
    action_lines = support.build_record_action_lines(
        active_view='jobs',
        record=snapshot.records[0],
        env='dev',
        feedback_lines=['结果: 成功'],
    )
    empty_record = support.build_empty_record()
    loading_section = support.build_loading_section()
    failure_sections = support.build_detail_load_failure_sections(RuntimeError('boom'), '任务详情')
    empty_section = support.build_empty_section()

    assert '当前页共 1 条记录' in summary
    assert '当前搜索 · sync' in filter_bar
    assert action is not None
    assert action.command_args == ('job', 'run-once', '1')
    assert any('结果: 成功' in line for line in action_lines)
    assert empty_record.title == tui_modules.cli_tui_copy.TUI_COPY.build_browser_empty_record_copy('title')
    assert loading_section.title == tui_modules.cli_tui_copy.TUI_COPY.build_browser_loading_copy('title')
    assert failure_sections[0].status == 'fail'
    assert failure_sections[0].title == '任务详情加载失败'
    assert empty_section.title == tui_modules.cli_tui_copy.TUI_COPY.build_detail_empty_section_copy('title')


def test_browser_screen_focus_service_moves_and_scrolls_between_focus_targets(
    tui_modules: SimpleNamespace,
) -> None:
    focus_service = tui_modules.cli_tui_browser.BrowserScreenFocusService()
    focused: list[str] = []
    sidebar = SimpleNamespace(focus=lambda: focused.append('sidebar'))
    navigator = SimpleNamespace(focus=lambda: focused.append('records'))
    sections = SimpleNamespace(focus=lambda: focused.append('sections'))
    summary = SimpleNamespace(focus=lambda: focused.append('summary'))
    detail = SimpleNamespace(focus=lambda: focused.append('detail'))
    workspace_main = object()
    fake_app = SimpleNamespace(focused=None)
    fake_screen = SimpleNamespace(
        app=fake_app,
        query_one=lambda selector, widget_type=None: {
            tui_modules.cli_tui_widgets.WorkspaceSidebar: sidebar,
            tui_modules.cli_tui_widgets.RecordNavigator: navigator,
            tui_modules.cli_tui_widgets.SectionNavigator: sections,
            tui_modules.cli_tui_widgets.RecordSummaryView: summary,
            tui_modules.cli_tui_widgets.SectionDetailView: detail,
            '#workspace-main': workspace_main,
        }[selector],
    )

    focus_service.move_focus(fake_screen, 1)
    fake_app.focused = navigator
    focus_service.move_focus(fake_screen, 1)
    fake_app.focused = object()
    scroll_target = focus_service.get_scroll_target(fake_screen)

    assert focused == ['sidebar', 'sections']
    assert scroll_target is workspace_main


def test_browser_screen_clear_search_remembers_empty_and_refreshes(
    monkeypatch: MonkeyPatch,
    tui_modules: SimpleNamespace,
) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='任务',
        subtitle='subtitle',
        records=[],
        shared_sections=[],
        filters=list(tui_modules.cli_tui_search.JOB_FILTER_OPTIONS),
        active_filter_key='all',
        search=tui_modules.cli_tui_search.PageSearchContext(
            placeholder='按任务名搜索',
            query='sync',
            suggestions=['sync-user'],
        ),
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='jobs',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    recorded_queries: list[tuple[str, str]] = []
    refresh_calls: list[str] = []
    fake_app = SimpleNamespace(
        remember_browser_query=lambda view_key, query: recorded_queries.append((view_key, query)),
        action_refresh_current_view=lambda: refresh_calls.append('refresh'),
    )
    monkeypatch.setattr(tui_modules.cli_tui_browser.BrowserScreen, 'app', property(lambda self: fake_app))

    screen.action_clear_search()

    assert recorded_queries == [('jobs', '')]
    assert refresh_calls == ['refresh']


def test_browser_screen_unmount_cancels_background_tasks(tui_modules: SimpleNamespace) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='缓存',
        subtitle='subtitle',
        records=[],
        shared_sections=[],
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='cache',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    cancelled: list[str] = []

    class DummyTask:
        def __init__(self, name: str) -> None:
            self.name = name

        def done(self) -> bool:
            return False

        def cancel(self) -> None:
            cancelled.append(self.name)

    screen._record_detail_task = DummyTask('detail')  # type: ignore[assignment]
    screen._action_task = DummyTask('action')  # type: ignore[assignment]
    request_id = screen._record_detail_request_id

    screen.on_unmount()

    assert cancelled == ['detail', 'action']
    assert screen._record_detail_request_id == request_id + 1


def test_browser_screen_failure_sections_use_current_snapshot_title(tui_modules: SimpleNamespace) -> None:
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='缓存',
        subtitle='subtitle',
        records=[],
        shared_sections=[],
    )
    screen = tui_modules.cli_tui_browser.BrowserScreen(
        snapshot,
        env='dev',
        active_view='cache',
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        refreshed_at='2026-04-30 10:00:00',
    )

    sections = screen._build_detail_load_failure_sections(RuntimeError('boom'))

    assert sections[0].title == '缓存详情加载失败'


@pytest.mark.asyncio
async def test_browser_screen_scrolls_in_small_viewport(tui_modules: SimpleNamespace) -> None:
    thin_scrollbar_size = 1
    app = tui_modules.cli_tui_app.ShierfengTuiApp('dev')
    app.action_show_dashboard = lambda: None  # type: ignore[method-assign]
    app.current_view = 'jobs'
    snapshot = tui_modules.cli_tui_adapters.BrowserPageSnapshot(
        title='任务',
        subtitle='超长浏览',
        records=[
            tui_modules.cli_tui_adapters.BrowserRecordSnapshot(
                key='job:1',
                title='任务一',
                status='warn',
                summary='摘要很长 摘要很长 摘要很长',
                metadata_lines=[f'字段{i}: 值很长 值很长 值很长' for i in range(1, 18)],
                detail_sections=[
                    tui_modules.cli_tui_adapters.DetailSectionSnapshot(
                        title='明细分区',
                        status='warn',
                        lines=[f'明细{i}: 说明很长 说明很长 说明很长' for i in range(1, 30)],
                    )
                ],
            )
            for _ in range(6)
        ],
        shared_sections=[],
    )
    screen = app.screen_factory.build(
        snapshot=snapshot,
        env=app.env,
        active_view=app.current_view,
        navigation_items=tui_modules.cli_tui_app.NAVIGATION_ITEMS,
        action_feedback_lines=app.get_action_feedback_lines(app.current_view),
    )

    async with app.run_test(size=(80, 24)) as pilot:
        app.screen_navigator.show(screen)
        await pilot.pause()
        await pilot.pause()

        main = app.screen.query_one('#workspace-main')
        browser_body = app.screen.query_one('#browser-body')
        browser_detail_pane = app.screen.query_one('#browser-detail-pane')
        record_navigator = app.screen.query_one(tui_modules.cli_tui_widgets.RecordNavigator)
        record_summary = app.screen.query_one(tui_modules.cli_tui_widgets.RecordSummaryView)
        section_detail = app.screen.query_one(tui_modules.cli_tui_widgets.SectionDetailView)

        assert main.styles.scrollbar_size_vertical == thin_scrollbar_size
        assert main.styles.scrollbar_size_horizontal == thin_scrollbar_size
        assert main.virtual_size.height > main.size.height
        assert browser_body.styles.scrollbar_size_vertical == thin_scrollbar_size
        assert browser_body.styles.scrollbar_size_horizontal == thin_scrollbar_size
        assert browser_body.virtual_size.width > browser_body.size.width
        assert browser_detail_pane.styles.scrollbar_size_vertical == thin_scrollbar_size
        assert browser_detail_pane.styles.scrollbar_size_horizontal == thin_scrollbar_size
        assert browser_detail_pane.virtual_size.height > browser_detail_pane.size.height
        assert record_navigator.styles.scrollbar_size_vertical == thin_scrollbar_size
        assert record_navigator.styles.scrollbar_size_horizontal == thin_scrollbar_size
        assert record_summary.styles.scrollbar_size_vertical == thin_scrollbar_size
        assert record_summary.styles.scrollbar_size_horizontal == thin_scrollbar_size
        assert section_detail.styles.scrollbar_size_vertical == thin_scrollbar_size
        assert section_detail.styles.scrollbar_size_horizontal == thin_scrollbar_size
