import asyncio
import logging
import os
import shutil
import sys
import time
from pathlib import Path

import pytest

from clarabot_nano.common.config.constants import CommonConfigConstants
from clarabot_nano.common.log import LoggingBootstrap
from clarabot_nano.utility.dirs import Directories
from clarabot_nano_tests.testing.client.manager import GraphQLClientManager
from clarabot_nano_tests.testing.file import rmtree_except
from clarabot_nano_tests.testing.server.manager import DevServerManager
from clarabot_nano_tests.testing.utility import alt_temp_dir
from dev_tools.build.deps.script import build_deps
from dev_tools.build.utility import dev_build_dir, dev_cache_dir
from dev_tools.picoture.script import build_picoture

# The user's temp directory may be sensitive on some platforms. To provide similar testing rules across platforms we
# explicitly override it, even if it is unnecessary.
os.environ["PYTEST_DEBUG_TEMPROOT"] = Path(alt_temp_dir(), "clarabot-nano-dev-test").as_posix()
os.makedirs(os.environ["PYTEST_DEBUG_TEMPROOT"], exist_ok=True)


@pytest.fixture(scope="session")
def testing_server_reset_data():
    async def _reset_testing_server():
        tsm = DevServerManager.create(os.environ.get("CLARABOT_TESTING_SERVER_CONFIG_PATH"))
        try:
            return await tsm.call_debug_reset("forty")
        finally:
            await tsm.close()

    return asyncio.run(_reset_testing_server())


@pytest.fixture(scope="session")
def testing_server_user0(testing_server_reset_data):
    return testing_server_reset_data["user_accounts"][0]


@pytest.fixture(scope="session")
def testing_server_user_1(testing_server_reset_data, testing_server_user0):
    last_user_data = testing_server_reset_data["user_accounts"][-1]
    assert testing_server_user0["_p_kr_version"] != last_user_data["_p_kr_version"]
    return last_user_data


@pytest.fixture(scope="session")
def testing_server_user_norm_pass(testing_server_reset_data):
    for ud in testing_server_reset_data["user_accounts"]:
        if ud.get("norm_alt_passes"):
            break
    else:
        assert False, "user missing with norm_alt_passes"
    return ud


@pytest.fixture(scope="session")
def testing_server_user_not_norm_pass(testing_server_reset_data):
    for ud in testing_server_reset_data["user_accounts"]:
        if ud.get("not_norm_alt_passes"):
            break
    else:
        assert False, "user missing with not_norm_alt_passes"
    return ud


@pytest.fixture
async def testing_server_manager():
    try:
        tsm = DevServerManager.create(os.environ.get("CLARABOT_TESTING_SERVER_CONFIG_PATH"))
        await tsm.csrf_by_session(await tsm.session())  # check if server listens
    except Exception as e:
        raise Exception("Debug server may not be available") from e
    try:
        yield tsm
    finally:
        await tsm.close()


@pytest.fixture
async def testing_client_manager(testing_server_manager):
    tcm = GraphQLClientManager(testing_server_manager)
    yield tcm


@pytest.fixture(scope="session", autouse=True)
def dev_build_nano_picoture():
    # Picoture is built by setuptools normally, for development and testing we auto-build it.
    return build_picoture(force=False)


@pytest.fixture(scope="session")
def dev_nano_build_dir():
    return dev_build_dir()


@pytest.fixture(scope="session")
def dev_nano_root_dir(dev_nano_build_dir, dev_build_nano_picoture):
    ds = Directories.instance()
    root_dir = ds.get_user_app_data_dir("devtest", "root")
    if root_dir.exists():
        shutil.rmtree(root_dir)
    build_deps(dev_cache_dir(), dev_nano_build_dir / "deps", dev_build_nano_picoture)
    shutil.copytree(dev_nano_build_dir, root_dir, dirs_exist_ok=True)
    # for base, dirs, files in os.walk(root_dir):
    #     os.chmod()
    return root_dir


@pytest.fixture(scope="session")
def monkeypatch_session():
    from _pytest.monkeypatch import MonkeyPatch

    m = MonkeyPatch()
    yield m
    m.undo()


@pytest.fixture(scope="session", autouse=True)
def monkey_patch_logging_bootstrap(monkeypatch_session):
    def _setup_logging_from_config(*args, **kwargs):
        print("NOOP setup_logging_from_config", *args, **kwargs)

    def _setup_logging(*args, **kwargs):
        print("NOOP setup_logging", *args, **kwargs)

    def _setup_custom_sigint_sigterm_handler(*args, **kwargs):
        print("NOOP setup_custom_sigint_sigterm_handler", *args, **kwargs)

    with monkeypatch_session.context() as m:
        m.setattr(LoggingBootstrap, "setup_logging_from_config", _setup_logging_from_config)
        m.setattr(LoggingBootstrap, "setup_logging", _setup_logging)
        m.setattr(LoggingBootstrap, "setup_custom_sigint_sigterm_handler", _setup_custom_sigint_sigterm_handler)
        yield


@pytest.fixture(autouse=True)
def ensure_test_logging_is_independent(monkeypatch):
    logger = logging.getLogger()
    assert str(logger.handlers).lower().find("concurrent") == -1, str(logger.handlers)


@pytest.fixture(autouse=True)
def ensure_testing_environment_setup(monkeypatch):
    assert (
        CommonConfigConstants.USER_DATA_PATH_PARTS[0] == "devtest"
    ), "Set CBNANO_USER_DIR_OVERRIDE to devtest for testing!"


@pytest.fixture
def user_app_dirs(ensure_testing_environment_setup):
    ds = Directories.instance()
    return ds.get_user_app_config_dir(*CommonConfigConstants.USER_DATA_PATH_PARTS), ds.get_user_app_data_dir(
        *CommonConfigConstants.USER_DATA_PATH_PARTS
    )


@pytest.fixture
def user_app_config_dir(user_app_dirs):
    return user_app_dirs[0]


@pytest.fixture
def user_app_data_dir(user_app_dirs):
    return user_app_dirs[1]


@pytest.fixture
def reset_user_app_dirs(user_app_dirs):
    config_dir, data_dir = user_app_dirs
    i = 0
    while True:
        try:
            if config_dir.exists():
                shutil.rmtree(config_dir)
            rmtree_except(data_dir, {"pid": None})
            break
        except PermissionError:
            if not sys.platform.startswith("win") or i == 4:
                raise
            time.sleep(0.5)
            i += 1
    return user_app_dirs


@pytest.fixture
def reset_user_app_config_dir(reset_user_app_dirs):
    return reset_user_app_dirs[0]


@pytest.fixture
def reset_user_app_data_dir(reset_user_app_dirs):
    return reset_user_app_dirs[1]
