import pytest

import windows
import windows.generated_def as gdef
from windows.pycompat import basestring

from .pfwtest import *

## This comment was in test_system.py: still revelant ?
# Well, pytest initialize COM with its own parameters
# It might make our own com.init() in WMI fail and therefore not call
# CoInitializeSecurity. But looks like pytest/default COM-security parameters
# does not allow to perform the request we want..
# So we try & do it ourself here.

pytestmark = pytest.mark.usefixtures("init_com_security")

@pytest.mark.parametrize("name, expected_cls", [
    ("root\\cimv2", "Win32_Process"),
    ("root\\subscription", "__EventFilter"),
    ])
def test_wmimanager_getnamespace(name, expected_cls):
    namespace = windows.system.wmi[name]
    assert namespace.name == name
    assert namespace.get_object(expected_cls)


def test_wmimanager_subnamespaces():
    subnamespaces = windows.system.wmi.get_subnamespaces("root")
    subnamespaces = [x.lower() for x in subnamespaces]
    assert "cimv2" in subnamespaces
    assert "security" in subnamespaces
    assert "subscription" in subnamespaces

# Test WmiNamespace

@pytest.mark.parametrize("name, query",[
    ("root\\cimv2", "select * from Win32_Process"),
    ("root\\subscription", "select * from __EventFilter"),
    ])
def test_query_select(name, query):
    namespace = windows.system.wmi[name]
    x = namespace.query(query)
    assert x
    assert isinstance(x, list)


def test_bad_query_raise():
    namespace = windows.system.wmi["root\\cimv2"]
    with pytest.raises(WindowsError) as e:
        x = namespace.query("BADSELECT QUERY BAD")
    assert (e.value.winerror & 0xffffffff) == gdef.WBEM_E_INVALID_QUERY

def test_create_class_enum():
    namespace = windows.system.wmi["root\\cimv2"]
    enum = namespace.create_class_enum(None)
    assert enum
    classes = list(enum)
    cls_names = [cls["__CLASS"].lower() for cls in classes]
    assert "win32_process" in cls_names
    assert "win32_shortcutfile" in cls_names
    assert "__win32provider" in cls_names

@pytest.mark.parametrize("name, cls", [
    ("root\\cimv2", "Win32_Process"),
    ("root\\subscription", "__EventFilter"),
    ])
def test_get_object(name, cls):
    namespace = windows.system.wmi[name]
    assert namespace.name == name
    obj = namespace.get_object(cls)
    assert obj["__CLASS"] == cls
    assert obj["__PATH"]

# Todo: test
#   - put_instance

@pytest.mark.parametrize("cmdline", [r"c:\windows\notepad.exe trolol.exe"])
def test_exec_method_Win32_Process_create(cmdline):
    namespace = windows.system.wmi["root\\cimv2"]
    win32_process_cls = namespace.get_object("Win32_Process")
    inparam = win32_process_cls.get_method("Create").inparam.spawn_instance()
    inparam["CommandLine"] = cmdline
    result = namespace.exec_method(win32_process_cls, "Create", inparam)
    assert result
    assert not result["ReturnValue"]
    assert result["ProcessId"]
    proc = windows.WinProcess(pid=result["ProcessId"])
    assert proc.peb.commandline.str == cmdline
    proc.exit(0)


## Test enum
def test_enumeration_iteration_no_timeout():
    namespace = windows.system.wmi["root\\cimv2"]
    processes = namespace.exec_query("select * from Win32_Process").all()
    assert isinstance(processes, list)
    assert processes
    processes = list(namespace.exec_query("select * from Win32_Process"))
    assert processes
    assert isinstance(processes, list)
    proc = namespace.exec_query("select * from Win32_Process").next()
    assert proc
    assert proc["__CLASS"].lower() == "win32_process"

def test_enumeration_iteration_timeout():
    namespace = windows.system.wmi["root\\cimv2"]
    timegen = namespace.exec_query("select * from Win32_Process").iter_timeout(0)
    # Iter on Win32_Process should not be immediat
    # so itering on timegen should trigger a timeout
    with pytest.raises(WindowsError) as e:
        x = list(timegen)
    assert (e.value.winerror & 0xffffffff) == gdef.WBEM_S_TIMEDOUT


@pytest.fixture
def wmi_cls():
    # Test expect the cls to have a "Name" attribute & "Create" method
    # Maybe doing something more generic
    namespace = windows.system.wmi["root\\cimv2"]
    yield namespace.get_object("Win32_Process")

def test_wmiobject_spawn(wmi_cls):
    assert wmi_cls["__Genus"] == wmi_cls.genus == gdef.WBEM_GENUS_CLASS
    wmi_obj = wmi_cls()
    assert wmi_obj["__Genus"] == wmi_obj.genus == gdef.WBEM_GENUS_INSTANCE
    assert wmi_obj["__CLASS"] == wmi_cls["__CLASS"]

def test_wmiobject_getitem(wmi_cls):
    assert wmi_cls["Name"] is None
    wmi_obj = wmi_cls()
    assert wmi_obj["Name"] is None
    with pytest.raises(WindowsError) as e:
        wmi_obj["BAD_NAME"]
    assert (e.value.winerror & 0xffffffff) == gdef.WBEM_E_NOT_FOUND
    # Complexe type
    assert isinstance(wmi_obj["__CLASS"], basestring)
    assert isinstance(wmi_obj["__PROPERTY_COUNT"], int)
    assert isinstance(wmi_obj["__DERIVATION"], list)

    props = wmi_obj.get_properties()
    assert isinstance(props, list)
    assert len(props) == wmi_obj["__PROPERTY_COUNT"]
    # Check that other dict-like methods exists
    assert wmi_obj.keys()
    assert wmi_obj.values()
    assert wmi_obj.items()


def test_wmiobject_getmethod(wmi_cls):
    wmi_method = wmi_cls.get_method("Create")
    # Wmi method is a custom PFW object (namedtuple)
    assert wmi_method
    assert wmi_method.inparam
    inparam_attrs = wmi_method.inparam.keys()
    assert "CommandLine" in inparam_attrs
    assert wmi_method.outparam
    outparam_attrs = wmi_method.outparam.keys()
    assert "ProcessId" in outparam_attrs

def test_wmiobject_setitem(wmi_cls):
    wmi_obj = wmi_cls()
    assert wmi_obj["Name"] is None
    wmi_obj["Name"] = "Test"
    assert wmi_obj["Name"] == "Test"
    # Strange but that how WMI api works with variant :D
    wmi_obj["Name"] = 2
    assert wmi_obj["Name"] == "2"
    with pytest.raises(WindowsError) as e:
        wmi_obj["PageFaults"] = "ERROR_BAD_INT"
    assert (e.value.winerror & 0xffffffff) == gdef.WBEM_E_TYPE_MISMATCH
    with pytest.raises(WindowsError) as e:
        wmi_obj["__PROPERTY_COUNT"] = 42
    assert (e.value.winerror & 0xffffffff) == gdef.WBEM_E_READ_ONLY




