import datetime

import pytest
from .pfwtest import *

import windows


testbasekeypath = r"HKEY_CURRENT_USER\SOFTWARE\PythonForWindows\Test"
basekeytest = windows.system.registry(testbasekeypath, gdef.KEY_WOW64_64KEY | gdef.KEY_ALL_ACCESS)

if not basekeytest.exists:
    basekeytest.create()

if windows.pycompat.is_py3:
    REG_TEST_BINARY_DATA = b"BIN_DATA\x01\x02\x03\x00" + bytes(range(256))
else:
    REG_TEST_BINARY_DATA = "BIN_DATA\x01\x02\x03\x00" + "".join(chr(i) for i in range(256))

@pytest.fixture()
def empty_test_base_key():
    assert basekeytest.exists
    # Depends on test_registry_key_empty :)
    basekeytest.empty()

# Clean registry before everytest
pytestmark = pytest.mark.usefixtures("empty_test_base_key")


@pytest.mark.parametrize("value", [1, "LOL", 0x11223344, ""])
def test_registry_set_get_simple_values(value):
    basekeytest["tst1"] = value
    assert basekeytest["tst1"].value == value


# TODO: test with other registry type (the stranges ones)
@pytest.mark.parametrize("value, type", [
    (0x11223344, gdef.REG_DWORD), # same as gdef.REG_DWORD_LITTLE_ENDIAN
    (0x11223344, gdef.REG_DWORD_BIG_ENDIAN),
    (0x1122334455667788, gdef.REG_QWORD), # same as gdef.REG_QWORD_LITTLE_ENDIAN
    ("", gdef.REG_SZ),
    ("Hello world", gdef.REG_SZ),
    ("Hello world %path%", gdef.REG_EXPAND_SZ),
    (["AAAA", "BBBB", "CCCC"], gdef.REG_MULTI_SZ),
    # Binary format and associated
    (REG_TEST_BINARY_DATA, gdef.REG_BINARY),
    (b"Hello-world", gdef.REG_LINK),
    (b"", gdef.REG_NONE),
    (b"Not really None :)\x11\x22\x00ABCD", gdef.REG_NONE),
    (b"Test-Unknown-format", 0x11223344), # Unknown registry type
    (b"Test-Unknown-format\x00\x01\xff\xfe Lol", 0xffffffff), # Unknown registry type
])
def test_registry_set_get_simple_values_with_types(value, type):
    basekeytest["tst2"] = (value, type)
    assert basekeytest["tst2"].value == value


@pytest.mark.parametrize("value, type", [
    # "\xff\xd8".decode("utf-16") -> UnicodeDecodeError
    (b"\xff\xd8", gdef.REG_MULTI_SZ),
    # Is NOT valid UTF-16 (len == 33)
    (b"Hello\x00World\x00This is not unicode\x00\x00", gdef.REG_MULTI_SZ),
    # Is valid UTF-16 (len == 40)
    # Should the decoding be completly different ?
    (b"Hello\x00World\x00This is not really unicode\x00\x00", gdef.REG_MULTI_SZ),
])
def test_registry_badly_encoded_values(value, type):
    # Bypass any encoding logic to setup bad key
    keyname = "bad_encoding"
    buffer = windows.utils.BUFFER(gdef.BYTE).from_buffer_copy(value)
    windows.winproxy.RegSetValueExW(basekeytest.phkey, keyname, 0, type, buffer, len(buffer))
    # Not the best decoded value
    # But should not crash
    assert basekeytest[keyname]



UNICODE_PATH_NAME = u'\u4e2d\u56fd\u94f6\u884c\u7f51\u94f6\u52a9\u624b'
UNICODE_RU_STRING = u"\u0441\u0443\u043a\u0430\u0020\u0431\u043b\u044f\u0442\u044c" # CYKA BLYAT in Cyrillic

# Could be done in test_registry_set_get_simple_values
# But was the cause a special bug / reimplem due to _winreg using ANSI functions
# So create a special test with a very identifiable name / bug cause

@pytest.mark.parametrize("unistr", [
    u'c:\\users\\hakril\\appdata\\local\\temp\\test_unicode_\u4e2d\u56fd\u94f6\u884c\u7f51\u94f6\u52a9\u624bdbqsm3',
    '\u52a9' * 126,
    UNICODE_PATH_NAME,
    UNICODE_RU_STRING,
    u""
])
def test_registry_unicode_string_value(unistr):
    basekeytest["tst3"] = unistr
    assert basekeytest["tst3"].value == unistr

@pytest.mark.parametrize("unistr", [
    # Looks like this value with this size MAY lead to non-existing NULL BYTE ?
    # This bug is tested in test_registry_Reg2Py_SZ
    u'c:\\users\\hakril\\appdata\\local\\temp\\test_unicode_\u4e2d\u56fd\u94f6\u884c\u7f51\u94f6\u52a9\u624bdbqsm3',
    u'\u52a9' * 126,
    UNICODE_PATH_NAME,
    UNICODE_PATH_NAME * 10,
    UNICODE_RU_STRING * 10,
    UNICODE_RU_STRING
])
def test_registry_unicode_string_values_enumeration(unistr):
    basekeytest["tst5"] = unistr
    values_by_name = {x.name: x for x in basekeytest.values}
    assert values_by_name["tst5"].value == unistr


def test_registry_unicode_multi_string():
    TST_MULTI = [UNICODE_PATH_NAME, "Hello World", UNICODE_RU_STRING]
    basekeytest["tst4"] = (TST_MULTI, gdef.REG_MULTI_SZ)
    assert basekeytest["tst4"].value == TST_MULTI


@pytest.mark.parametrize("unistr", [UNICODE_PATH_NAME, UNICODE_RU_STRING])
def test_registry_unicode_value_name(unistr):
    basekeytest[unistr] = 42
    assert basekeytest[unistr].value == 42
    # assert unistr in [v.name for v in basekeytest.values]
    del basekeytest[unistr]

def test_registry_subkeys_create_delete():
    subname = "MyTestSubKey"
    subkey = basekeytest(subname)
    assert not subkey.exists
    subkey.create()
    assert subkey.exists
    subkey.delete()
    assert not subkey.exists

def test_registry_get_key_info():
    subname = "MyTestSubKeySizeInfo"
    subkey = basekeytest(subname).create()
    subkey["A"] = "12345"
    subkey["AAAA"] = "1"
    max_name_size, max_value_size = subkey.get_key_size_info()
    assert max_name_size == 4 # AAAA
    assert max_value_size == 6 * 2 # 12345\x00 -> 2 BYTE per char (utf-16)
    other_info = subkey.info
    assert other_info[0] == 0 # Nb subkeys
    assert other_info[1] == 2 # Nb values
    assert isinstance(other_info[2], windows.pycompat.int_types) # Last write

def test_registry_key_empty():
    subname = "MyTestKeyEmpty"
    subkey = basekeytest(subname).create()
    # Add some non-empty subkeys
    subkey("AAA").create()["LOL"] = 2
    subkey("BBBB").create()["value"] = "hello"
    # Add some values
    subkey["42"] = 43
    subkey["XXX"] = "42_42"
    assert subkey.values
    assert subkey.subkeys
    subkey.empty()
    # Check everythin disappeared
    assert not subkey.values
    assert not subkey.subkeys


def test_registry_unicode_value_name_enumerate():
    name1 = u"enum_" + UNICODE_PATH_NAME
    name2 = u"enum_" + UNICODE_RU_STRING
    basekeytest[name1] = 1
    basekeytest[name2] = 2
    values_names = [v.name for v in basekeytest.values]
    assert name1 in values_names
    assert name2 in values_names


class CustomCountForRegistryTest(object):
    TESTKEY = None

    def __init__(self):
        self.value = 0


    def __iter__(self):
        while True:
            print("NEXT")
            if self.value == 1:
                print("ADDING HARDCODE KEY !")
                self.TESTKEY[BIG_KEY_NAME] = BIG_KEY_VALUE
            yield self.value
            self.value += 1

BIG_KEY_NAME = "BIG" * 50
BIG_KEY_VALUE = "BIG" * 0x2000

def test_registry_unicode_value_name_enumerate_with_race_condition(monkeypatch):
    import itertools
    # With itertools.count() to add a big key in the middle of the enumeration
    # With a bigger name & data that the key currently existing

    # Create a new subkey so that the KeyInfos are "reset"
    subkeyname = str(datetime.datetime.now())
    assert not basekeytest(subkeyname).exists
    subkey = basekeytest(subkeyname).create()
    try:
        CustomCountForRegistryTest.TESTKEY = subkey
        monkeypatch.setattr(itertools, "count", CustomCountForRegistryTest)
        name1 = u"enum_" + UNICODE_PATH_NAME
        name2 = u"enum_" + UNICODE_RU_STRING
        subkey[name1] = 1
        subkey[name2] = 2
        values_names = [v.name for v in subkey.values]
        assert name1 in values_names
        assert name2 in values_names
        assert BIG_KEY_NAME in values_names
    finally:
        subkey.delete()

def test_registry_unicode_subkeys_create_delete():
    if windows.pycompat.is_py3:
        subname =  UNICODE_RU_STRING + str(datetime.datetime.now())
    else:
        subname =  UNICODE_RU_STRING + unicode(datetime.datetime.now())
    subkey = basekeytest(subname)
    assert not subkey.exists
    subkey.create()
    assert subkey.exists
    subkey.delete()
    assert not subkey.exists


def test_registry_unicode_subkeys_enumerate():
    name1 = u"subkey" + UNICODE_PATH_NAME
    name2 = u"subkey" + UNICODE_RU_STRING
    basekeytest(name1).create()
    basekeytest(name2).create()
    subkey_names = [sk.name for sk in basekeytest.subkeys]
    assert name1 in subkey_names
    assert name2 in subkey_names

original_RegEnumValueW = windows.winproxy.RegEnumValueW

def fake_RegEnumValueW_fill_but_raise(hKey, dwIndex, lpValueName, lpcchValueName, lpReserved, lpType, lpData, lpcbData):
    print("fake_RegEnumValueW")
    result = original_RegEnumValueW(hKey, dwIndex, lpValueName, lpcchValueName, lpReserved, lpType, lpData, lpcbData)
    raise windows.winproxy.WinproxyError("fake_RegEnumValueW", gdef.ERROR_MORE_DATA)
    return result

def test_registry_win_bug_RegEnumValueW_1(monkeypatch):
    # import pdb; pdb.set_trace()
    # Found a bug on some computers where RegEnumValueW would fill the data but also ERROR_MORE_DATA
    basekeytest["VALUE_1"] = "LOOOL"
    basekeytest["VALUE_2"] = 42
    basekeytest["XXX" * 0x100] = 42
    assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}
    monkeypatch.setattr(windows.winproxy, "RegEnumValueW", fake_RegEnumValueW_fill_but_raise)
    # Bug make it hang here..
    assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}
    print("LOL")

def fake_RegEnumValueW_always_raise(hKey, dwIndex, lpValueName, lpcchValueName, lpReserved, lpType, lpData, lpcbData):
    print("fake_RegEnumValueW_always_raise")
    raise windows.winproxy.WinproxyError("fake_RegEnumValueW", gdef.ERROR_MORE_DATA)

def test_registry_win_bug_RegEnumValueW_2(monkeypatch):
    # Found a bug on some computers where RegEnumValueW would fill the data but also ERROR_MORE_DATA
    # This case here should never happen, but better an exception that an infinite loop
    basekeytest["VALUE_1"] = "LOOOL"
    basekeytest["VALUE_2"] = 42
    basekeytest["XXX" * 0x100] = 42
    assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}
    monkeypatch.setattr(windows.winproxy, "RegEnumValueW", fake_RegEnumValueW_always_raise)
    # Bug make it hang here..
    with pytest.raises(ValueError):
        # A bug that do not allow is to extract the values will raises to be explicit..
        assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}

original_basekeytest_get_key_size = basekeytest.get_key_size_info

def bad_get_key_valuesize():
    namesize, valuesize = original_basekeytest_get_key_size()
    return namesize, valuesize - 3

def bad_get_key_namesize():
    namesize, valuesize = original_basekeytest_get_key_size()
    return namesize - 3, valuesize

def test_registry_win_bug_get_key_size_info_valuesize_too_small(monkeypatch):
    basekeytest["VALUE_1"] = "LOOOL"
    basekeytest["VALUE_2"] = 42
    basekeytest["XXX" * 0x100] = 42

    assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}
    monkeypatch.setattr(basekeytest, "get_key_size_info", bad_get_key_valuesize)
    assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}

def test_registry_win_bug_get_key_size_info_namesize_too_small(monkeypatch):
    basekeytest["VALUE_1"] = "LOOOL"
    basekeytest["VALUE_2"] = 42
    basekeytest["XXX" * 0x100] = 42

    assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}
    monkeypatch.setattr(basekeytest, "get_key_size_info", bad_get_key_namesize)
    assert set(x.name for x in basekeytest.values) == {"VALUE_1", "VALUE_2", "XXX" * 0x100}
