import os
from pathlib import Path

import pytest
import safe_radix32

from clarabot_nano.common.crypto.admin_password import ADMIN_PASSWORD_CONTEXT
from clarabot_nano.common.spawn.constants import ShortNames
from clarabot_nano.core.config.utility import block_name_from_config_format
from clarabot_nano_tests.core.utility.common import create_children_wait_map
from clarabot_nano_tests.testing.config import parse_components_for_block_name
from clarabot_nano_tests.testing.core.instance import CoreTestingNanoInstanceHandler
from clarabot_nano_tests.testing.file import temp_path


def test_core_config_utilities():
    id_hash = "2467z86822222:0GLPOdcixTh3W0e/j9TUfEPaXl398pvTvN/w12miXS8="
    name = "2467z86822222|0GLPOdcixTh3W0e_j9TUfEPaXl398pvTvN_w12miXS8="
    assert block_name_from_config_format(id_hash) == name
    assert block_name_from_config_format(name) == name


@pytest.mark.asyncio
async def test_core_request_config_updates(tmpdir, reset_user_app_dirs, dev_nano_root_dir):
    restricted_paths = [str(temp_path(tmpdir)) for _ in range(3)]
    other_paths = [str(temp_path(tmpdir)) for _ in range(3)]

    def _mut_loc_conf(c_):
        c_["drive_restriction_paths"] = restricted_paths

    async with CoreTestingNanoInstanceHandler(
        reset_user_app_dirs, dev_nano_root_dir, local_config_mut=_mut_loc_conf
    ).run() as (
        pm,
        session,
        children_spawns,
        dbg_config,
    ):
        gateway_pid, gateway_info = next(iter(children_spawns[ShortNames.GATEWAY].items()))

        # test rename nano
        cv = dbg_config["remote"]["name"]
        for v, ok in [
            (["yes"], False),
            ("", True),
            ("test őzike", True),
            (541, False),
        ]:
            await pm.assert_request_config_update(
                session, gateway_info["port"], {"_handler_key": "name", "name": v}, ok=ok
            )
            if ok:
                cv = v
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["name"] == cv
        # extra request with bad message packing
        await pm.assert_request_config_update(session, gateway_info["port"], {}, ok=False, message=b"xxxxxx")
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test set admin password
        cv = dbg_config["remote"]["admin_password"]
        for v, ok in [
            (["ty"], False),
            ("00000", True),
            (541, False),
            (98526, False),
            (985261, False),
            ("máj-pawwörd-iz-beter-dan-jourz", True),
        ]:
            await pm.assert_request_config_update(
                session, gateway_info["port"], {"_handler_key": "admin_password", "admin_password": v}, ok=ok
            )
            if ok:
                cv = str(v)
            dbg_config = pm.get_dbg_config()
            if cv:
                assert ADMIN_PASSWORD_CONTEXT.verify(cv, dbg_config["remote"]["admin_password"])
            else:
                assert not dbg_config["remote"]["admin_password"]
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test set deny-anonymous flag
        cv = dbg_config["remote"]["deny_anonymous"]
        for v, ok in [
            (["ty"], False),
            ("00000", False),
            (True, True),
            (985261, False),
            (False, True),
        ]:
            await pm.assert_request_config_update(
                session, gateway_info["port"], {"_handler_key": "deny_anonymous", "deny_anonymous": v}, ok=ok
            )
            if ok:
                cv = v
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["deny_anonymous"] == cv
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test set backup-interval
        cv = dbg_config["remote"]["backup_interval"]
        for v, ok in [
            (["ty"], False),
            ("00000", False),
            (True, False),
            (172800, True),
            (85261, True),
            (3600, True),
            (3599, False),
            (172801, False),
            (43200, True),
        ]:
            await pm.assert_request_config_update(
                session, gateway_info["port"], {"_handler_key": "backup_interval", "backup_interval": v}, ok=ok
            )
            if ok:
                cv = v
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["backup_interval"] == cv
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # check secrets regeneration
        cv = dbg_config["remote"]["secrets"]
        await pm.assert_request_config_update(session, gateway_info["port"], {"_handler_key": "secrets"})
        dbg_config = pm.get_dbg_config()
        assert not set(dbg_config["remote"]["secrets"]).intersection(cv)

        # test set require_explicit_peer_trust flag
        cv = dbg_config["remote"]["require_explicit_peer_trust"]
        for v, ok in [
            (["ty"], False),
            ("00000", False),
            (True, True),
            (985261, False),
            (False, True),
        ]:
            await pm.assert_request_config_update(
                session,
                gateway_info["port"],
                {"_handler_key": "require_explicit_peer_trust", "require_explicit_peer_trust": v},
                ok=ok,
            )
            if ok:
                cv = v
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["require_explicit_peer_trust"] == cv
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test set indexed-languages
        cv = dbg_config["remote"]["indexed_languages"]
        for v, ok in [
            ("ALL", False),
            ("all", True),
            (541, False),
            ("", False),
            ([], True),
            ("hu", False),
            (["hu", "RU", "Jp"], True),
            (["hu", 5842, "Jp"], False),
        ]:
            await pm.assert_request_config_update(
                session, gateway_info["port"], {"_handler_key": "indexed_languages", "indexed_languages": v}, ok=ok
            )
            if ok:
                cv = v
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["indexed_languages"] == cv
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test create drive
        t_paths = [
            str(Path(restricted_paths[0]) / "ta"),
            str(restricted_paths[1]),
            str(Path(restricted_paths[2]) / "tc"),
        ]
        os.makedirs(t_paths[0])
        os.makedirs(t_paths[1])
        os.makedirs(other_paths[1])
        o_file_p = Path(restricted_paths[0]) / "ff"
        o_file_p.write_bytes(b"test")
        sentinel = object()
        cv = dbg_config["remote"]["drives"]
        cr = dbg_config["remote"]["room_blocks"]
        for v, ok, uv in [
            (str(o_file_p), False, sentinel),
            (t_paths[2], False, sentinel),
            ("\\not even valid", False, sentinel),
            (541, False, sentinel),
            (t_paths[0], True, [{"path": t_paths[0]}]),
            (t_paths[0], False, sentinel),
            (f"{os.path.sep}.{os.path.sep}".join(t_paths[0].rsplit(os.path.sep, 1)), False, sentinel),
            ("", False, sentinel),
            (".", False, sentinel),
            ([], False, sentinel),
            (t_paths[1], True, [{"path": t_paths[0]}, {"path": t_paths[1]}]),
            ("hu", False, sentinel),
            (other_paths[0], False, sentinel),
            (other_paths[1], False, sentinel),
        ]:
            if ok:
                result = await pm.assert_core_do_drive_create(session, gateway_info["port"], v)
                assert result == {"ok": ok}, str(v)
                cv = uv
            else:
                await pm.assert_request_config_update(
                    session, gateway_info["port"], {"_handler_key": "drive:create", "path": v}, ok=ok
                )
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["drives"] == cv
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test attach room to drive
        for v, ok, r, bs in [
            (t_paths[2], False, "222", [parse_components_for_block_name(88400, b"asdf")]),
            ("\\not even valid", False, "222", [parse_components_for_block_name(88400, b"asdf")]),
            (541, False, "222", [parse_components_for_block_name(88400, b"asdf")]),
            (t_paths[0], False, 210, [parse_components_for_block_name(88400, b"asdf")]),
            (t_paths[0], False, None, [parse_components_for_block_name(88400, b"asdf")]),
            (t_paths[0], False, "210", [parse_components_for_block_name(88400, b"asdf")]),
            (t_paths[0], True, "223", [parse_components_for_block_name(88401, b"asdf1")]),
            (t_paths[0], False, ["asdf"], [parse_components_for_block_name(88400, b"asdf")]),
            (t_paths[0], True, "233", [parse_components_for_block_name(88402, b"asdf2")]),
            ("", False, "222", [parse_components_for_block_name(88400, b"asdf")]),
            (".", False, "222", [parse_components_for_block_name(88400, b"asdf")]),
            ([], False, "222", [parse_components_for_block_name(88400, b"asdf")]),
            (t_paths[1], True, "44444", [parse_components_for_block_name(88403, b"asdf3")]),
            (t_paths[0], False, "223", [parse_components_for_block_name(88400, b"asdf")]),
            (t_paths[0], False, "224", [parse_components_for_block_name(88400, b"asdf").replace("|", "_")]),
            (
                t_paths[0],
                True,
                "224",
                [parse_components_for_block_name(88404, b"asdf4"), parse_components_for_block_name(88405, b"asdf5")],
            ),
            ("hu", False, "222", [parse_components_for_block_name(88400, b"asdf")]),
        ]:
            if ok:
                result = await pm.assert_core_do_drive_attach_room(session, gateway_info["port"], v, r, bs)
                assert result == {"ok": ok}
                for drive in cv:
                    if drive["path"] == v:
                        if "rooms" not in drive:
                            drive["rooms"] = []
                        drive["rooms"].append(r)
                        break
                else:
                    assert False
                cr[r] = bs
            else:
                await pm.assert_request_config_update(
                    session,
                    gateway_info["port"],
                    {"_handler_key": "drive:add-resource", "path": v, "resource": r},
                    ok=ok,
                )
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["drives"] == cv
            assert dbg_config["remote"]["room_blocks"] == cr
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test dead room cleanup
        await pm.assert_request_config_update(
            session, gateway_info["port"], {"_handler_key": "cleanup-dead-rooms"}, ok=False
        )
        for ok in [True, False]:
            await pm.assert_request_config_update(
                session,
                gateway_info["port"],
                {
                    "_handler_key": "cleanup-dead-rooms",
                    "dead_ids": ["4424444", "44444", 987656488777, safe_radix32.decode("224"), {"asd": "xy"}, -1],
                },
                ok=ok,
            )
        for drive in cv:
            drive["rooms"] = [r for r in drive["rooms"] if r != "224"]
        dbg_config = pm.get_dbg_config()
        assert dbg_config["remote"]["drives"] == cv
        assert dbg_config["remote"]["room_blocks"] == cr
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test detach room from drive
        for v, ok, r in [
            (t_paths[2], False, "222"),
            ("\\not even valid", False, "222"),
            (541, False, "222"),
            (t_paths[0], False, 513),
            (t_paths[0], False, None),
            (t_paths[0], False, "44442"),
            (t_paths[0], True, "223"),
            (t_paths[0], False, ["asdf"]),
            ("", False, "222"),
            (".", False, "222"),
            ([], False, "222"),
            (t_paths[1], True, "44444"),
            (t_paths[0], False, "223"),
            (t_paths[0], False, "224"),  # detached by dead cleanup test
            ("hu", False, "222"),
        ]:
            if ok:
                result = await pm.assert_core_do_drive_detach_room(session, gateway_info["port"], v, r)
                assert result == {"ok": ok}
                for drive in cv:
                    if drive["path"] == v:
                        if "rooms" not in drive:
                            drive["rooms"] = []
                        drive["rooms"].remove(r)
                        break
                else:
                    assert False
            else:
                await pm.assert_request_config_update(
                    session,
                    gateway_info["port"],
                    {"_handler_key": "drive:remove-resource", "path": v, "resource": r},
                    ok=ok,
                )
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["drives"] == cv
            assert dbg_config["remote"]["room_blocks"] == cr
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test delete drive
        for v, ok in [
            (t_paths[2], False),
            ("\\not even valid", False),
            (541, False),
            (t_paths[0], True),
            ("", False),
            (".", False),
            ([], False),
            (t_paths[1], True),
            ("hu", False),
        ]:
            if ok:
                result = await pm.assert_core_do_drive_delete(session, gateway_info["port"], v)
                assert result == {"ok": ok}
                for drive in cv:
                    if drive["path"] == v:
                        cv.remove(drive)
                        break
                else:
                    assert False
            else:
                await pm.assert_request_config_update(
                    session, gateway_info["port"], {"_handler_key": "drive:delete", "path": v}, ok=ok
                )
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["drives"] == cv
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)

        # test set server process number
        cv = dbg_config["remote"]["server_process_number"]
        for v, ok in [
            (["ty"], False),
            (1, True),
            (0, False),
            (3, True),
            (None, False),
        ]:
            await pm.assert_request_config_update(
                session,
                gateway_info["port"],
                {"_handler_key": "server_process_number", "server_process_number": v},
                ok=ok,
            )
            if ok:
                cv = v
                children_spawns = await pm.wait_children(
                    create_children_wait_map(general_server_number=cv), timeout=10.0
                )
            dbg_config = pm.get_dbg_config()
            assert dbg_config["remote"]["server_process_number"] == cv
        await pm.wait_children_config(session, children_spawns, fixed_config=dbg_config)
