import datetime
import os
import shutil
import sys
from pathlib import Path
from win32com.client import Dispatch
from win32com.server.exception import COMException
from win32com.propsys import propsys, pscon


def get_ext(filename: str) -> str:
    return filename[filename.rfind('.')+1:]

def is_supported(filename: str) -> bool:
    return get_ext(filename).lower() in ['jpg', 'jpeg', 'gif', 'mov', 'mp4', 'png']

def is_video_file(filename: str) -> bool:
    return get_ext(filename).lower() in ['mov', 'mp4']

def create_target_directory(timestamp):
    date = datetime.datetime.fromtimestamp(timestamp)
    target_directory = f'{os.getcwd()}/{date.year}/{date.month}/{date.day}'
    Path(target_directory).mkdir(parents=True, exist_ok=True)
    return target_directory

def copy_and_remove(file_info, destination):
    # video files seem to freeze on move/remove
    # so we copy and only remove non-video files
    shutil.copy2(file_info.Path, destination)
    if not is_video_file(file_info.Name):
        os.remove(file_info.Path)

def create_directory_and_move_file(timestamp, file_info):
    target_directory = create_target_directory(timestamp)
    destination = os.path.abspath(f'{target_directory}/{file_info.Name}')
    if os.path.isfile(destination):
        return False
    copy_and_remove(file_info, destination)
    return True

def create_timestamp_from_date(datetime_as_text: str):
    date_and_time = datetime_as_text.split()
    date = date_and_time[0].split('/')
    time = date_and_time[1].split(':')
    return datetime.datetime(int(date[2]), int(date[1]), int(date[0]), int(time[0]), int(time[1])).timestamp()

def get_details(namespace, _item):
    _details = {}
    for j in range(0, 150):
        try:
            _detail = namespace.GetDetailsOf(_item, j)
            if _detail:
                _details[namespace.GetDetailsOf(j, j)] = _detail
        except COMException:
            print(f"\nStopped getting details at index: {j}")
            break
    return _details

def get_creation_and_modification_time(details):
    creation_time = create_timestamp_from_date(details.get("Date created"))
    date_taken = details.get("Date taken")
    comments = details.get("Comments")
    if date_taken:
        date_taken = create_timestamp_from_date(date_taken.replace('\u200e', '').replace('\u200f', ''))
        return creation_time, date_taken
    elif comments and "timestamp" in comments:
        return creation_time, int(comments[comments.rfind('=')+1:])
    return creation_time, None

def try_get_media_created_date(item):
    properties = propsys.SHGetPropertyStoreFromParsingName(item.Path)
    dt_property = properties.GetValue(pscon.PKEY_Media_DateEncoded).GetValue()
    if dt_property:
        return dt_property.timestamp()

if __name__ == "__main__":
    shell = Dispatch("Shell.Application")
    ns = shell.NameSpace(os.getcwd())
    copied = 0
    duplicates = 0
    fails = 0
    print(f"Starting work...")
    for item in ns.Items():
        if is_supported(item.Name):
            try:
                creation_time, modification_time = get_creation_and_modification_time(get_details(ns, item))
                if not modification_time:
                    modification_time = try_get_media_created_date(item)
                if modification_time:
                    os.utime(item.Path, (creation_time, modification_time))
                    result = create_directory_and_move_file(modification_time, item)
                    copied += result
                    duplicates += not result
                else:
                    fails += 1
                    print(f"\n!!! Not enough info in file: {item.Name}")
            except Exception as e:
                fails += 1
                print(f"\nError while processing {item.Name}\nError:{e}")
            sys.stdout.write('.')
            sys.stdout.flush()
    print(f"\n\nDone - copied {copied} files, found {duplicates} duplicates, failed on {fails} files")
