import sys
import pytest
import os
import tempfile

from datetime import datetime, timedelta
import windows.utils
import windows.generated_def as gdef
from .pfwtest import *

if sys.version_info.major >= 3:
    unicode = str

pytestmark = pytest.mark.usefixtures('check_for_gc_garbage')

ntqueryinformationfile_info_structs = {
    gdef.FileAccessInformation: gdef.FILE_ACCESS_INFORMATION,
    gdef.FileAlignmentInformation: gdef.FILE_ALIGNMENT_INFORMATION,
    gdef.FileAllInformation: gdef.FILE_ALL_INFORMATION,
    gdef.FileAttributeTagInformation: gdef.FILE_ATTRIBUTE_TAG_INFORMATION,
    gdef.FileBasicInformation: gdef.FILE_BASIC_INFORMATION,
    gdef.FileEaInformation: gdef.FILE_EA_INFORMATION ,
    gdef.FileInternalInformation: gdef.FILE_INTERNAL_INFORMATION,
    gdef.FileIoPriorityHintInformation: gdef.FILE_IO_PRIORITY_HINT_INFORMATION,
    gdef.FileModeInformation: gdef.FILE_MODE_INFORMATION,
    gdef.FileNetworkOpenInformation: gdef.FILE_NETWORK_OPEN_INFORMATION,
    gdef.FileNameInformation: gdef.FILE_NAME_INFORMATION,
    gdef.FilePositionInformation: gdef.FILE_POSITION_INFORMATION,
    gdef.FileStandardInformation: gdef.FILE_STANDARD_INFORMATION,
    gdef.FileIsRemoteDeviceInformation: gdef.FILE_IS_REMOTE_DEVICE_INFORMATION,
}

def test_query_file_information():
    f = open(r"C:\windows\system32\ntdll.dll")
    handle = windows.utils.get_handle_from_file(f)
    for info_class, info_struct in ntqueryinformationfile_info_structs.items():
        res = windows.utils.query_file_information(handle, info_class) # Fail should raise
        resf = windows.utils.query_file_information(f, info_class) # Try with the file directly
        # Check return type
        assert isinstance(res, info_struct)
        assert isinstance(resf, info_struct)


ntqueryvolumeinformationfile_info_structs = {
    gdef.FileFsAttributeInformation: gdef.FILE_FS_ATTRIBUTE_INFORMATION,
    gdef.FileFsControlInformation: gdef.FILE_FS_CONTROL_INFORMATION,
    gdef.FileFsDeviceInformation: gdef.FILE_FS_DEVICE_INFORMATION,
    # gdef.FileFsDriverPathInformation: gdef.FILE_FS_DRIVER_PATH_INFORMATION, # Not handled for now
    gdef.FileFsFullSizeInformation: gdef.FILE_FS_FULL_SIZE_INFORMATION,
    gdef.FileFsObjectIdInformation: gdef.FILE_FS_OBJECTID_INFORMATION,
    gdef.FileFsSizeInformation: gdef.FILE_FS_SIZE_INFORMATION,
    gdef.FileFsVolumeInformation: gdef.FILE_FS_VOLUME_INFORMATION,
    gdef.FileFsSectorSizeInformation: gdef.FILE_FS_SECTOR_SIZE_INFORMATION,
}

def test_query_volume_information():
    f = open(r"C:\windows\system32\ntdll.dll")
    handle = windows.utils.get_handle_from_file(f)

    for info_class, info_struct in ntqueryvolumeinformationfile_info_structs.items():
        res = windows.utils.query_volume_information(handle, info_class) # Fail should raise
        resf = windows.utils.query_volume_information(f, info_class) # Try with the file directly
        # Check return type
        assert isinstance(res, info_struct)
        assert isinstance(resf, info_struct)

def date_equals(d1, d2, acceptable_delta=timedelta(microseconds=1)):
    if d2 > d1:
        delta = d2 - d1
    else:
        delta = d1 - d2
    return delta <= acceptable_delta

def test_datetime_from_filetime():
    FILENAME = "C:\\windows\\system32\\ntdll.dll"
    f = open(FILENAME)

    datetime_from_filetime = windows.utils.datetime_from_filetime

    # Compare to os.stat
    stats = os.stat(FILENAME)
    utc_satime = datetime.utcfromtimestamp(stats.st_atime)
    utc_smtime = datetime.utcfromtimestamp(stats.st_mtime)
    utc_sctime = datetime.utcfromtimestamp(stats.st_ctime)

    # Compare to NtQueryInformationFile
    fileinfo = windows.utils.query_file_information(f, gdef.FileBasicInformation)

    utc_watime = datetime_from_filetime(fileinfo.LastAccessTime)
    utc_wmtime = datetime_from_filetime(fileinfo.LastWriteTime)
    utc_wctime = datetime_from_filetime(fileinfo.CreationTime)

    assert date_equals(utc_satime, utc_watime)
    assert date_equals(utc_smtime, utc_wmtime)
    assert date_equals(utc_sctime, utc_wctime)

    # Test rounding
    ## Round-down


    assert date_equals(datetime_from_filetime(131492395680727300), datetime(2017, 9, 7, 6, 32, 48, 72730))
    assert date_equals(datetime_from_filetime(131492395680727304), datetime(2017, 9, 7, 6, 32, 48, 72730))

    assert date_equals(datetime_from_filetime(131492395680727305), datetime(2017, 9, 7, 6, 32, 48, 72731))
    assert date_equals(datetime_from_filetime(131492395680727309), datetime(2017, 9, 7, 6, 32, 48, 72731))

    assert date_equals(datetime.utcfromtimestamp(1504764215.5896280), datetime_from_filetime(131492378155896280))
    assert date_equals(datetime.utcfromtimestamp(1504764215.5896284), datetime_from_filetime(131492378155896284))
    assert date_equals(datetime.utcfromtimestamp(1504764215.5896285), datetime_from_filetime(131492378155896285))
    assert date_equals(datetime.utcfromtimestamp(1504764215.5896289), datetime_from_filetime(131492378155896289))



def test_unix_timestamp_from_filetime():
    # Check date vs timestamps to be sure
    assert datetime.utcfromtimestamp(1504765968.072730) == datetime(2017, 9, 7, 6, 32, 48, 72730)
    assert windows.utils.unix_timestamp_from_filetime(131492395680727300) == 1504765968.072730
    assert windows.utils.unix_timestamp_from_filetime(131492395680727304) == 1504765968.072730

    assert datetime.utcfromtimestamp(1504765968.072731) == datetime(2017, 9, 7, 6, 32, 48, 72731)
    assert windows.utils.unix_timestamp_from_filetime(131492395680727309) == 1504765968.072731
    # Well py3 will round it to 1504765968.07273
    # Because round(0.5) == 0 (vs 1 in py2)
    assert windows.utils.unix_timestamp_from_filetime(131492395680727305) == 1504765968.072731

# Test values from https://docs.microsoft.com/en-us/cpp/atl-mfc-shared/date-type?view=vs-2019
@pytest.mark.parametrize("comtime, date", [
    (0, datetime(1899, 12, 30)),
    (2, datetime(1900, 1, 1)),
    (5, datetime(1900, 1, 4)),
    (5.25, datetime(1900, 1, 4, hour=6)),
    (5.5, datetime(1900, 1, 4, hour=12)),
    (5.875, datetime(1900, 1, 4, hour=21)),
    (-0.25, datetime(1899, 12, 30, hour=6)),
    (-0.5, datetime(1899, 12, 30, hour=12)),
    (-2, datetime(1899, 12, 28)),
    (-2.5, datetime(1899, 12, 28, hour=12)),
    (-2.75, datetime(1899, 12, 28, hour=18)),
])
def test_datetime_from_comtime(comtime, date):
    assert windows.utils.datetime_from_comdate(comtime) == date

@pytest.mark.parametrize("prefix", [
    ("long_ascii_prefix"),
    (u'\u4e2d\u56fd\u94f6\u884c\u7f51\u94f6\u52a9\u624b'),
    ])
def test_long_short_path_str_unicode(prefix):
    """Test that get_short_path/get_long_path works with str/unicode path and preserve path type"""
    with tempfile.NamedTemporaryFile(prefix=prefix) as f:
        basename = f.name.lower()
        short_name = windows.utils.get_short_path(basename).lower()
        assert isinstance(short_name, unicode)
        assert short_name != basename
        full_name = windows.utils.get_long_path(short_name).lower()
        assert isinstance(full_name, unicode)
        assert full_name == basename

TEST_CERT = b"""
MIIBwTCCASqgAwIBAgIQG46Uyws+67ZBOfPJCbFrRjANBgkqhkiG9w0BAQsFADAfMR0wGwYDVQQD
ExRQeXRob25Gb3JXaW5kb3dzVGVzdDAeFw0xNzA0MTIxNDM5MjNaFw0xODA0MTIyMDM5MjNaMB8x
HTAbBgNVBAMTFFB5dGhvbkZvcldpbmRvd3NUZXN0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB
gQCRHwC/sRfXh5pc4poc85aidrudbPdya+0OeonQlf1JQ1ekf7KSfADV5FLkSQu2BzgBK9DIWTGX
XknBJIzZF03UZsVg5D67V2mnSClXucc0cGFcK4pDDt0tHeabA2GPinVe7Z6qDT4ZxPR8lKaXDdV2
Pg2hTdcGSpqaltHxph7G/QIDAQABMA0GCSqGSIb3DQEBCwUAA4GBACcQFdOlVjYICOIyAXowQaEN
qcLpN1iWoL9UijNhTY37+U5+ycFT8QksT3Xmh9lEIqXMh121uViy2P/3p+Ek31AN9bB+BhWIM6PQ
gy+ApYDdSwTtWFARSrMqk7rRHUveYEfMw72yaOWDxCzcopEuADKrrYEute4CzZuXF9PbbgK6"""


def test_sprint_certificate():
    cert = windows.crypto.Certificate.from_buffer(b64decode(TEST_CERT))
    # Certificate is quite a complexe Windows structure
    # With Sub-struct / Pointer & string
    # It was broken on py3 -> ense this test
    windows.utils.sprint(cert)
