import pytest
from traitlets.config import Config

from jupyter_server.serverapp import ServerApp
from jupyter_server.utils import run_sync

from .mockextensions.app import MockExtensionApp


@pytest.fixture
def jp_server_config(jp_template_dir):
    config = {
        "ServerApp": {
            "jpserver_extensions": {"tests.extension.mockextensions": True},
        },
        "MockExtensionApp": {
            "template_paths": [str(jp_template_dir)],
            "log_level": "DEBUG",
        },
    }
    return config


@pytest.fixture
def mock_extension(extension_manager):
    name = "tests.extension.mockextensions"
    pkg = extension_manager.extensions[name]
    point = pkg.extension_points["mockextension"]
    app = point.app
    return app


def test_initialize(jp_serverapp, jp_template_dir, mock_extension):
    # Check that settings and handlers were added to the mock extension.
    assert isinstance(mock_extension.serverapp, ServerApp)
    assert len(mock_extension.handlers) > 0
    assert mock_extension.loaded
    assert mock_extension.template_paths == [str(jp_template_dir)]


@pytest.mark.parametrize(
    "trait_name, trait_value, jp_argv",
    (
        [
            "mock_trait",
            "test mock trait",
            ["--MockExtensionApp.mock_trait=test mock trait"],
        ],
    ),
)
def test_instance_creation_with_argv(
    trait_name,
    trait_value,
    jp_argv,
    mock_extension,
):
    assert getattr(mock_extension, trait_name) == trait_value


def test_extensionapp_load_config_file(
    config_file,
    jp_serverapp,
    mock_extension,
):
    # Assert default config_file_paths is the same in the app and extension.
    assert mock_extension.config_file_paths == jp_serverapp.config_file_paths
    assert mock_extension.config_dir == jp_serverapp.config_dir
    assert mock_extension.config_file_name == "jupyter_mockextension_config"
    # Assert that the trait is updated by config file
    assert mock_extension.mock_trait == "config from file"


def test_extensionapp_no_parent():
    # make sure we can load config files, even when serverapp is not passed
    # relevant for e.g. shortcuts to config-loading
    app = MockExtensionApp()
    assert isinstance(app.config_file_paths, list)
    assert app.serverapp is not None


OPEN_BROWSER_COMBINATIONS = (
    (True, {}),
    (True, {"ServerApp": {"open_browser": True}}),
    (False, {"ServerApp": {"open_browser": False}}),
    (True, {"MockExtensionApp": {"open_browser": True}}),
    (False, {"MockExtensionApp": {"open_browser": False}}),
    (
        True,
        {
            "ServerApp": {"open_browser": True},
            "MockExtensionApp": {"open_browser": True},
        },
    ),
    (
        False,
        {
            "ServerApp": {"open_browser": True},
            "MockExtensionApp": {"open_browser": False},
        },
    ),
    (
        True,
        {
            "ServerApp": {"open_browser": False},
            "MockExtensionApp": {"open_browser": True},
        },
    ),
    (
        False,
        {
            "ServerApp": {"open_browser": False},
            "MockExtensionApp": {"open_browser": False},
        },
    ),
)


@pytest.mark.parametrize("expected_value, config", OPEN_BROWSER_COMBINATIONS)
def test_browser_open(monkeypatch, jp_environ, config, expected_value):
    serverapp = MockExtensionApp.initialize_server(config=Config(config))
    assert serverapp.open_browser == expected_value


def test_load_parallel_extensions(monkeypatch, jp_environ):
    serverapp = MockExtensionApp.initialize_server()
    exts = serverapp.extension_manager.extensions
    assert "tests.extension.mockextensions.mock1" in exts
    assert "tests.extension.mockextensions" in exts

    exts = serverapp.jpserver_extensions
    assert exts["tests.extension.mockextensions.mock1"]
    assert exts["tests.extension.mockextensions"]


def test_stop_extension(jp_serverapp, caplog):
    """Test the stop_extension method.

    This should be fired by ServerApp.cleanup_extensions.
    """
    calls = 0

    # load extensions (make sure we only have the one extension loaded
    jp_serverapp.extension_manager.load_all_extensions()
    extension_name = "tests.extension.mockextensions"
    assert list(jp_serverapp.extension_manager.extension_apps) == [extension_name]

    # add a stop_extension method for the extension app
    async def _stop(*args):
        nonlocal calls
        calls += 1

    for apps in jp_serverapp.extension_manager.extension_apps.values():
        for app in apps:
            if app:
                app.stop_extension = _stop

    # call cleanup_extensions, check the logging is correct
    caplog.clear()
    run_sync(jp_serverapp.cleanup_extensions())
    assert [msg for *_, msg in caplog.record_tuples] == [
        "Shutting down 1 extension",
        f'{extension_name} | extension app "mockextension" stopping',
        f'{extension_name} | extension app "mockextension" stopped',
    ]

    # check the shutdown method was called once
    assert calls == 1
