import asyncio
import os
import time
from pathlib import Path

import pytest
import safe_radix32

from clarabot_nano.utility import parse_environment_flag
from clarabot_nano_tests.gateway.inter.common import BackupTestingObserver
from clarabot_nano_tests.gateway.inter.utility import run_nano_setup_drive, check_fs_recursive
from clarabot_nano_tests.testing.client.manager import GraphQLClientManager
from clarabot_nano_tests.testing.core.common import clear_nano_slots
from clarabot_nano_tests.testing.file import temp_path
from clarabot_nano_tests.testing.privy.manager import DevPrivyManager
from clarabot_nano_tests.testing.server.manager import DevServerManager


@pytest.mark.skipif(parse_environment_flag("CBNANO_TEST_WITHOUT_SERVER"), reason="server-dependant tests disabled")
@pytest.mark.skipif(not parse_environment_flag("CBNANO_TEST_WITH_PRIVY"), reason="privy-dependant tests not enabled")
@pytest.mark.asyncio
async def test_gateway_backup_agent(
    tmpdir,
    reset_user_app_dirs,
    reset_user_app_dirs_override2,
    dev_nano_root_dir,
    testing_server_manager: DevServerManager,
    testing_server_user0,
    testing_client_manager: GraphQLClientManager,
    testing_server_reset_data,
    testing_privy_manager: DevPrivyManager,
):
    u0_id_sr32 = safe_radix32.encode_fw(testing_server_user0["account_id"])
    u0_kr = testing_server_user0["_acc_kr"]
    await clear_nano_slots(testing_server_manager, [testing_server_user0["account_id"]])

    u1_data = testing_server_reset_data["user_accounts"][1]
    u2_data = testing_server_reset_data["user_accounts"][2]  # NOTE: has nano license as well
    u3_data = testing_server_reset_data["user_accounts"][3]

    u0_rooms = [testing_server_user0["rooms"][i] for i in range(4)]
    u2_room = u2_data["rooms"][0]

    invalid_room_id = safe_radix32.encode_fw(u0_rooms[-1]["id"])  # NOTE: not actually invalid, but unused
    session = await testing_server_manager.session()  # debug client session

    u0_drive0 = str(temp_path(tmpdir))
    for dp in [u0_drive0]:
        os.makedirs(dp)

    async with run_nano_setup_drive(
        testing_server_manager,
        testing_client_manager,
        reset_user_app_dirs,
        dev_nano_root_dir,
        testing_server_user0,
        u0_drive0,
        u0_rooms[0],
    ) as (u0wsc0, u0a_sess0, u0mut_name0, u0nano_id0):
        # wait for privy instance
        print(" !!! ATTENTION !!!")
        print(" START PRIVY FOR TESTING")
        print("")
        print(" Waiting...")
        await testing_privy_manager.get_status_message(timeout=90, sleep=0.5)
        prc = await testing_privy_manager.get_remote_config()
        assert not prc.get("completed")
        privy_room_id, privy_peer_kr, privy_room_kr = await testing_privy_manager.setup_privy_room(
            testing_client_manager, u0wsc0
        )
        privy_room_id_sr32 = safe_radix32.encode_fw(privy_room_id)
        privy_roots = [Path(str(temp_path(tmpdir))) for _ in range(3)]
        virtual_root = Path(str(temp_path(tmpdir)))
        virtual_root.mkdir(parents=True)
        for privy_root in privy_roots:
            privy_root.mkdir(parents=True)
            (virtual_root / privy_root.name).symlink_to(privy_root, target_is_directory=True)
        await testing_privy_manager.configure_drives(privy_roots, ["*.bak"])

        bto = BackupTestingObserver(
            testing_client_manager,
            u0mut_name0,
            u0a_sess0,
            u0nano_id0,
            u0wsc0,
            u0_kr,
            str(virtual_root),
            u0_drive0,
            privy_room_id_sr32,
            invalid_room_id,
        )

        const_data = os.urandom(16)

        for i, pr in enumerate(privy_roots):
            Path(pr, f"d{i}_f0_const").write_bytes(const_data)
            Path(pr, f"d{i}_f0_b0").write_bytes(b"")
            Path(pr, f"d{i}_f0_b8").write_bytes(os.urandom(8))
            Path(pr, f"d{i}_f0_b2mib").write_bytes(os.urandom(4096) * 512)
            Path(pr, f"d{i}_f0_b20mib").write_bytes(
                os.urandom(4096) + b"".join([bytes([i] * 1024) * 1024 + os.urandom(4096) for i in range(20)])
            )

        await bto.wait_finish(room_id=privy_room_id_sr32)

        # check some extra handlers functionality
        await testing_client_manager.get_privy_info(privy_room_id, u0wsc0, u0_kr, privy_peer_kr, privy_room_kr)

        prc = await testing_privy_manager.get_remote_config()
        assert prc.get("completed")
        assert (
            len(prc["completed"]) == 1
            and prc["completed"][0]["name"]
            and prc["completed"][0]["matches"] is True
            and abs(time.time() - prc["completed"][0]["timestamp"]) < 10
        ), str(prc)

        async def _request_wait_manual_backup(n_):
            bto.read_status(require=True)
            manual_backup_path_ = bto.path / bto.next_folder
            print(" !!! ATTENTION !!!")
            print(" REQUEST BACKUP FROM PRIVY")
            t = time.perf_counter()
            while True:
                if time.perf_counter() - t > 90:
                    assert False, "Manual backup did not occur"
                prc_ = await testing_privy_manager.get_remote_config()
                if len(prc_["completed"]) == n_:
                    break
                await asyncio.sleep(0.5)

            check_fs_recursive(bto.remote_path, manual_backup_path_)

        for i, pr in enumerate(privy_roots):
            Path(pr, f"d{i}d0", "d01").mkdir(parents=True)
            Path(pr, f"d{i}d0", "d01", "f01_b0").write_bytes(b"")
            Path(pr, f"d{i}d0", "d01", "f01_b8").write_bytes(os.urandom(8))
            Path(pr, f"d{i}d1", "d11").mkdir(parents=True)
            Path(pr, f"d{i}d1", "d12").mkdir(parents=True)
            Path(pr, f"d{i}d1", "f0_bx").write_bytes(os.urandom(4500) * 377)
            Path(pr, f"d{i}d2").mkdir(parents=True)
            Path(pr, f"d{i}d2", "f1_b0").write_bytes(b"")
            Path(pr, f"d{i}d3").mkdir(parents=True)

        await _request_wait_manual_backup(2)

        for i, pr in enumerate(privy_roots):
            Path(pr, f"d{i}_f0_b0").unlink()
            Path(pr, f"d{i}_f0_b8").unlink()
            Path(pr, f"d{i}d1", "d12").rmdir()
            Path(pr, f"d{i}d3").rmdir()
            Path(pr, f"d{i}d1", "d11", "fxx0").write_bytes(os.urandom(8))

        await _request_wait_manual_backup(3)
