from contextlib import asynccontextmanager

from clarabot_nano.common.spawn.constants import ShortNames
from clarabot_nano_tests.testing.core.manager import DevCoreProcessManager
from clarabot_nano_tests.testing.gui import GUI_AVAILABLE


@asynccontextmanager
async def start_nano_debug(testing_server_manager, user_app_dirs, nano_root_dir, core_args=(), extra_env=None):
    """
    Start the Nano Instance in debug mode and return instantly. (processes are still starting)
    """
    with DevCoreProcessManager.startup(
        *user_app_dirs,
        "clarabot_nano.development.core",
        args=["--development_server_config", str(testing_server_manager.config_path)] + list(core_args),
        env={**(extra_env or {}), "CBNANO_ROOT": str(nano_root_dir)},
    ) as pm:
        yield pm


@asynccontextmanager
async def start_nano_wait_ready(
    testing_server_manager,
    user_app_dirs,
    nano_root_dir,
    core_args=(),
    with_control_api=False,
    with_document_server=False,
    extra_env=None,
):
    """
    Start the Nano Instance in debug mode and wait for all processes to have started up.
    """
    session = await testing_server_manager.session()  # debug client session
    async with start_nano_debug(testing_server_manager, user_app_dirs, nano_root_dir, core_args, extra_env) as pm:
        children_spawns = await pm.wait_children(
            {
                ShortNames.GENERAL_SERVER: 1,
                ShortNames.FSTX_SERVER: 1,
                ShortNames.DOCUMENT_SERVER: int(with_document_server),
                ShortNames.INDEXER: 1,
                ShortNames.GATEWAY: 1,
                ShortNames.CONTROL: int("--headless" not in core_args and GUI_AVAILABLE),
                ShortNames.CONTROL_API: int(with_control_api),
                ShortNames.DEBUG: 1,
            }
        )
        await pm.wait_children_config(session, children_spawns)
        debug_pid, debug_info = next(iter(children_spawns[ShortNames.DEBUG].items()))
        yield pm, debug_info


@asynccontextmanager
async def start_nano_wait_gateway(testing_server_manager, user_app_dirs, nano_root_dir, gateway_status, extra_env=None):
    async with start_nano_wait_ready(testing_server_manager, user_app_dirs, nano_root_dir, extra_env=extra_env) as (
        pm,
        debug_info,
    ):
        # initially waiting for config
        state = await pm.wait_dbg_states(
            [
                ("gateway_port", lambda v: isinstance(v, int)),
                ("gateway_key", lambda v: isinstance(v, bytes)),
                ("gateway_status", gateway_status),
            ],
            timeout=20.0 if gateway_status == "active" else 5.0,
        )
        gateway_port = state["gateway_port"]
        gateway_key = state["gateway_key"]

        yield pm, debug_info, gateway_port, gateway_key


async def do_gateway_login(
    testing_server_manager, pm, debug_info, gateway_port, gateway_key, server_user, wait_active_session=True
):
    session = await testing_server_manager.session()  # debug client session
    last_command_number = (await pm.wait_dbg_states({})).get("gateway_command_result_number")
    result = await pm.assert_core_do_login(session, debug_info["port"], server_user["email"], server_user["password"])
    assert result == "login successful", str(result)
    state = await pm.wait_dbg_states(
        [
            ("gateway_port", gateway_port),
            ("gateway_key", gateway_key),
            ("gateway_status", lambda v: v != "waiting-config"),
            ("gateway_command_result", "login successful"),
            ("gateway_command_result_number", lambda v: v != last_command_number),
        ]
    )
    last_command_number = state["gateway_command_result_number"]
    await pm.wait_dbg_states(
        [
            ("gateway_port", gateway_port),
            ("gateway_key", gateway_key),
            ("gateway_status", "connected"),
        ],
        timeout=15.0,
    )
    if wait_active_session:
        await pm.wait_dbg_states(
            [
                ("gateway_port", gateway_port),
                ("gateway_key", gateway_key),
                ("gateway_status", "active"),
            ],
            timeout=35.0,
        )
        assert isinstance(session_id := pm.get_session_id_from_continue_token(), int)
        return session_id


async def do_gateway_logout(testing_server_manager, pm, debug_info, gateway_port, gateway_key):
    session = await testing_server_manager.session()  # debug client session
    last_command_number = (await pm.wait_dbg_states({})).get("gateway_command_result_number")
    result = await pm.assert_core_do_logout(session, debug_info["port"])
    assert result == "logout successful", str(result)
    state = await pm.wait_dbg_states(
        [
            ("gateway_port", gateway_port),
            ("gateway_key", gateway_key),
            ("gateway_status", lambda v: v == "waiting-config"),
            ("gateway_command_result", "logout successful"),
            ("gateway_command_result_number", lambda v: v != last_command_number),
        ]
    )
    last_command_number = state["gateway_command_result_number"]


@asynccontextmanager
async def start_nano_wait_login(testing_server_manager, user_app_dirs, nano_root_dir, server_user):
    async with start_nano_wait_gateway(testing_server_manager, user_app_dirs, nano_root_dir, "waiting-config") as (
        pm,
        debug_info,
        gateway_port,
        gateway_key,
    ):
        await testing_server_manager.wait_nano_bind(server_user["account_id"], {})
        assert pm.get_session_id_from_continue_token() is None

        session_id = await do_gateway_login(
            testing_server_manager, pm, debug_info, gateway_port, gateway_key, server_user
        )

        yield pm, debug_info, gateway_port, gateway_key, session_id
