

from typing import Set
#import shutil
from pathlib import Path
import subprocess
import importlib
import time
import json

import bpy
from bpy_extras import asset_utils
from bpy.types import Context, Operator
from bpy.props import (
    BoolProperty, 
    EnumProperty, 
    StringProperty, 
    IntProperty)

#from asset_library.constants import (DATA_TYPES, DATA_TYPE_ITEMS, MODULE_DIR)
import asset_library
from asset_library.common.bl_utils import (
    get_addon_prefs, 
    get_bl_cmd, 
    #suitable_areas, 
    refresh_asset_browsers,
    load_datablocks)

from asset_library.common.file_utils import open_blender_file, synchronize
from asset_library.common.functions import get_active_library, asset_warning_callback

from textwrap import dedent
from tempfile import gettempdir


class ASSETLIB_OT_clear_asset(Operator):
    bl_idname = "assetlib.clear_asset"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Clear Asset'
    bl_description = 'Clear Selected Assets'

    @classmethod
    def poll(cls, context):
        if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
            return False

        sp = context.space_data
        if sp.params.asset_library_ref == 'LOCAL':
            return False

        return True

    def execute(self, context: Context) -> Set[str]:
        asset = context.active_file
    
        lib = get_active_library()

        filepath = lib.adapter.format_path(asset.asset_data['filepath'])
        asset_image = lib.adapter.get_path('image', asset.name, filepath)
        asset_video = lib.adapter.get_path('video', asset.name, filepath)

        if filepath:
            if filepath.exists():
                filepath.unlink()
        if asset_image:
            asset_image.unlink()
        if asset_video:
            asset_video.unlink()
        #open_blender_file(filepath)

        bpy.ops.assetlib.bundle(name=lib.name, blocking=True)

        return {'FINISHED'}


class ASSETLIB_OT_edit_data(Operator):
    bl_idname = "assetlib.edit_data"
    bl_label = "Edit Asset Data"
    bl_description = "Edit Current Asset Data"
    bl_options = {"REGISTER", "UNDO"}

    warning: StringProperty(name='')
    path: StringProperty(name='Path')
    catalog: StringProperty(name='Catalog', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'})
    name: StringProperty(name='Name', update=asset_warning_callback, options={'TEXTEDIT_UPDATE'})
    tags: StringProperty(name='Tags', description='Tags need to separate with a comma (,)')

    @classmethod
    def poll(cls, context):
        if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
            return False
        return True

    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()

        lib = get_active_library()
        if lib.merge_libraries:
            lib = prefs.libraries[lib.store_library]

        new_name = lib.adapter.norm_file_name(self.name)
        new_asset_path = lib.adapter.get_asset_path(name=new_name, catalog=self.catalog)

        self.old_asset_path.unlink()
        lib.adapter.write_asset(asset=self.asset, asset_path=new_asset_path)

        if self.old_image_path.exists():
            new_img_path = lib.adapter.get_path('image', new_name, new_asset_path)
            self.old_image_path.rename(new_img_path)

        if self.old_video_path.exists():
            new_video_path = lib.adapter.get_path('video', new_name, new_asset_path)
            self.old_video_path.rename(new_video_path)

        if self.old_description_path.exists():
            self.old_description_path.unlink()

        new_asset_description = lib.adapter.get_asset_description(
            asset=self.asset,
            catalog=self.catalog,
            modified=time.time_ns()
        )

        lib.adapter.write_description_file(new_asset_description, new_asset_path)

        if not list(self.old_asset_path.parent.iterdir()):
            self.old_asset_path.parent.rmdir()

        diff_path = Path(bpy.app.tempdir, 'diff.json')

        diff = [dict(self.old_asset_description, operation='REMOVE')]
        diff += [dict(lib.adapter.norm_asset_datas([new_asset_description])[0], operation='ADD')]

        diff_path.write_text(json.dumps(diff, indent=4), encoding='utf-8')

        bpy.ops.assetlib.bundle(name=lib.name, diff=str(diff_path), blocking=True)

        return {"FINISHED"}

    def draw(self, context):
        layout = self.layout
        layout.separator()

        layout.use_property_split = True

        lib = get_active_library()

        if lib.merge_libraries:
            layout.prop(lib, 'store_library', expand=False)

        layout.prop(self, "catalog", text="Catalog")
        layout.prop(self, "name", text="Name")
        layout.prop(self, 'tags')

        #layout.prop()

        layout.separator()
        col = layout.column()
        col.use_property_split = False
        #row.enabled = False

        if self.path:
            col.label(text=self.path)

        if self.warning:
            col.label(icon='ERROR', text=self.warning)

    def invoke(self, context, event):

        lib = get_active_library()

        active_lib = lib.adapter.get_active_asset_library()

        lib.store_library = active_lib.name

        asset_handle = context.asset_file_handle

        catalog_file = lib.adapter.read_catalog()
        catalog_ids = {v['id']: {'path': k, 'name': v['name']} for k,v in catalog_file.items()}

        #asset_handle = context.asset_file_handle
        self.old_asset_name = asset_handle.name
        self.old_asset_path = lib.adapter.get_active_asset_path()
        
        self.asset = load_datablocks(self.old_asset_path, self.old_asset_name, type=lib.data_types)

        self.old_image_path = lib.adapter.get_path('image', self.old_asset_name, self.old_asset_path)
        self.old_video_path = lib.adapter.get_path('video', self.old_asset_name, self.old_asset_path)

        self.old_description_path = lib.adapter.get_description_path(self.old_asset_path)
        
        self.old_asset_description = lib.adapter.read_asset_description_file(self.old_asset_path)
        self.old_asset_description = lib.adapter.norm_asset_datas([self.old_asset_description])[0]


        if not self.asset:
            self.report({'ERROR'}, 'No asset found')

        self.name = self.old_asset_name
        self.tags = ', '.join(self.asset.asset_data.tags.keys())
        #asset_path
        self.catalog = catalog_ids[asset_handle.asset_data.catalog_id]['path']


        return context.window_manager.invoke_props_dialog(self)

    def cancel(self, context):
        print('Cancel Edit Data, removing the asset')

        lib = get_active_library()
        active_lib = lib.adapter.get_active_asset_library()

        getattr(bpy.data, active_lib.data_types).remove(self.asset)

class ASSETLIB_OT_remove_user_library(Operator):
    bl_idname = "assetlib.remove_user_library"
    bl_options = {"REGISTER", "UNDO"}
    bl_label = 'Remove User Library'
    bl_description = 'Remove User Library'

    index : IntProperty(default=-1)

    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()

        prefs.user_libraries.remove(self.index)

        return {'FINISHED'}


class ASSETLIB_OT_add_user_library(Operator):
    bl_idname = "assetlib.add_user_library"
    bl_options = {"REGISTER", "UNDO"}
    bl_label = 'Add User Library'
    bl_description = 'Add User Library'


    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()

        lib = prefs.user_libraries.add()
        lib.expand = True

        return {'FINISHED'}


class ASSETLIB_OT_open_blend(Operator):
    bl_idname = "assetlib.open_blend"
    bl_options = {"REGISTER", "UNDO"}
    bl_label = 'Open Blender File'
    bl_description = 'Open blender file'

    #filepath : StringProperty(subtype='FILE_PATH') 

    def execute(self, context: Context) -> Set[str]:
        #asset = context.active_file
        #prefs = get_addon_prefs()

        lib = get_active_library()

        #filepath = lib.adapter.format_path(asset.asset_data['filepath'])

        filepath = lib.adapter.get_active_asset_path()

        open_blender_file(filepath)

        return {'FINISHED'}


class ASSETLIB_OT_set_paths(Operator):
    bl_idname = "assetlib.set_paths"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Set Paths'
    bl_description = 'Set Library Paths'

    name: StringProperty()
    all: BoolProperty(default=False)

    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()
        print('Set Paths')
        if self.all:
            libs = prefs.libraries
        else:
            libs = [prefs.libraries[self.name]]
        
        for lib in libs:
            lib.clear_library_path()
            lib.set_library_path()

        return {'FINISHED'}


class ASSETLIB_OT_bundle_library(Operator):
    bl_idname = "assetlib.bundle"
    bl_options = {"INTERNAL"}
    bl_label = 'Bundle Library'
    bl_description = 'Bundle all matching asset found inside one blend'

    name : StringProperty()
    diff : StringProperty()
    blocking : BoolProperty(default=False)
    mode : EnumProperty(items=[(i.replace(' ', '_').upper(), i, '') for i in ('None', 'All', 'Auto Bundle')], default='NONE')
    directory : StringProperty(subtype='DIR_PATH')
    #conform : BoolProperty(default=False)
    #def refresh(self):
    #    for area in suitable_areas(bpy.context.screen):
    #        bpy.ops.asset.library_refresh({"area": area, 'region': area.regions[3]})
            #space_data.activate_asset_by_id(asset, deferred=deferred)

    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()

        libs = []
        if self.name:
            libs += [prefs.libraries[self.name]]

        if self.mode == 'ALL':
            libs += prefs.libraries.values()
        elif self.mode == 'AUTO_BUNDLE':
            libs += [l for l in prefs.libraries if l.auto_bundle]
        
        if not libs:
            return {"CANCELLED"}
                
        lib_datas = [l.to_dict() for l in libs]

        print(f'Bundle Libraries: {[l.name for l in libs]}')

        script_code = dedent(f"""
            import bpy
            prefs = bpy.context.preferences.addons["asset_library"].preferences

            for lib_data in {lib_datas}:
                lib = prefs.env_libraries.add()
                lib.set_dict(lib_data)
                lib.adapter.bundle(cache_diff='{self.diff}')
            
            bpy.ops.wm.quit_blender()
            """)

        script_path = Path(bpy.app.tempdir) / 'bundle_library.py'
        script_path.write_text(script_code)

        print(script_code)

        #raise Exception()

        cmd = get_bl_cmd(script=str(script_path), background=True)

        #print(cmd) 
        if self.blocking:
            subprocess.call(cmd)
            bpy.app.timers.register(refresh_asset_browsers, first_interval=0.2)
        else:
            subprocess.Popen(cmd)
        
        return {'FINISHED'}


class ASSETLIB_OT_reload_addon(Operator):
    bl_idname = "assetlib.reload_addon"
    bl_options = {"UNDO"}
    bl_label = 'Reload Asset Library Addon'
    bl_description = 'Reload The Asset Library Addon and the addapters'

    def execute(self, context: Context) -> Set[str]:

        print('Execute reload')

        asset_library.unregister()
        importlib.reload(asset_library)
        asset_library.register()

        return {'FINISHED'}


class ASSETLIB_OT_diff(Operator):
    bl_idname = "assetlib.diff"
    bl_options = {"REGISTER", "UNDO"}
    bl_label = 'Synchronize'
    bl_description = 'Synchronize Action Lib to Local Directory'

    name : StringProperty()
    conform : BoolProperty(default=False)

    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()

        lib = prefs.libraries.get(self.name)

        if self.conform:
            lib.conform.adapter.diff()
        else:
            lib.adapter.diff()

        return {'FINISHED'}

'''
class ASSETLIB_OT_conform_library(Operator):
    bl_idname = "assetlib.conform_library"
    bl_options = {"REGISTER", "UNDO"}
    bl_label = "Conform Library"
    bl_description = "Split each assets per blend and externalize preview"

    name : StringProperty()
    template_image : StringProperty()
    template_video : StringProperty()
    directory : StringProperty(subtype='DIR_PATH', name='Filepath')

    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()

        lib = prefs.libraries.get(self.name)
        #lib.adapter.conform(self.directory)

        templates = {}
        if self.template_image:
            templates['image'] = self.template_image
        if self.template_video:
            templates['video'] = self.template_video


        script_path = Path(bpy.app.tempdir) / 'bundle_library.py'
        script_code = dedent(f"""
            import bpy
            prefs = bpy.context.preferences.addons["asset_library"].preferences
            lib = prefs.env_libraries.add()
            lib.set_dict({lib.to_dict()})
            lib.adapter.conform(directory='{self.directory}', templates={templates})
            """)

        script_path.write_text(script_code)

        cmd = get_bl_cmd(script=str(script_path), background=True)

        subprocess.Popen(cmd)

        return {'FINISHED'}
    
    def invoke(self, context, event):
        context.window_manager.fileselect_add(self)
        return {'RUNNING_MODAL'}
'''

class ASSETLIB_OT_generate_previews(Operator):
    bl_idname = "assetlib.generate_previews"
    bl_options = {"REGISTER", "UNDO"}
    bl_label = "Generate Previews"
    bl_description = "Generate and write the image for assets"

    cache : StringProperty()
    preview_blend : StringProperty()
    name : StringProperty()
    blocking : BoolProperty(default=True)

    def execute(self, context: Context) -> Set[str]:
        prefs = get_addon_prefs()
        lib = prefs.libraries.get(self.name)
        # self.write_file(self.diff_file, self.diff)

        # preview_assets = [(a.asset_data['filepath'], self.data_types, a.name) for a in assets]

        # self.preview_assets_file.write_text(json.dumps(preview_assets), encoding='utf-8')

        # cmd = [
        #     bpy.app.binary_path, '-b', '--use-system-env',
        #     '--python', str(PREVIEW_ASSETS_SCRIPT), '--',
        #     '--preview-blend', str(self.preview_blend), 
        #     '--preview-assets-file', str(self.preview_assets_file)
        # ]
        # subprocess.call(cmd)
        preview_blend = self.preview_blend or lib.adapter.preview_blend
        
        if not preview_blend or not Path(preview_blend).exists():
            preview_blend = MODULE_DIR / 'common' / 'preview.blend'

        script_path = Path(bpy.app.tempdir) / 'generate_previews.py'
        script_code = dedent(f"""
            import bpy
            prefs = bpy.context.preferences.addons["asset_library"].preferences
            lib = prefs.env_libraries.add()
            lib.set_dict({lib.to_dict()})

            bpy.ops.wm.open_mainfile(filepath='{preview_blend}', load_ui=True)
            lib.adapter.generate_previews(cache='{self.cache}')
            """)

        script_path.write_text(script_code)

        cmd = get_bl_cmd(script=str(script_path), background=True)

        if self.blocking:
            subprocess.call(cmd)
        else:
            subprocess.Popen(cmd)


        return {'FINISHED'}

class ASSETLIB_OT_play_preview(Operator):
    bl_idname = "assetlib.play_preview"
    bl_options = {"REGISTER", "UNDO", "INTERNAL"}
    bl_label = 'Play Preview'
    bl_description = 'Play Preview'

    @classmethod
    def poll(cls, context: Context) -> bool:
        if not context.active_file:
            return False

        if not asset_utils.SpaceAssetInfo.is_asset_browser(context.space_data):
            cls.poll_message_set("Current editor is not an asset browser")
            return False

        lib = get_active_library()
        if not lib:
            return False

        return True

    def execute(self, context: Context) -> Set[str]:
        asset = context.active_file
        prefs = get_addon_prefs()

        lib = get_active_library()

        #filepath = lib.adapter.format_path(asset.asset_data['filepath'])
        asset_path = lib.adapter.get_active_asset_path()

        asset_image = lib.adapter.get_image(asset.name, asset_path)
        asset_video = lib.adapter.get_video(asset.name, asset_path)
        
        if not asset_image and not asset_video:
            self.report({'ERROR'}, f'Preview for {asset.name} not found.')
            return {"CANCELLED"}
        
        if asset_video:
            self.report({'INFO'}, f'Video found. {asset_video}.')

            if prefs.video_player:
                subprocess.Popen([prefs.video_player, asset_video])
            else:
                bpy.ops.wm.path_open(filepath=str(asset_video))
        else:
            self.report({'INFO'}, f'Image found. {asset_image}.')

            if prefs.image_player:
                subprocess.Popen([prefs.image_player, asset_image])
            else:
                bpy.ops.wm.path_open(filepath=str(asset_image))


        return {"FINISHED"}


class ASSETLIB_OT_synchronize(Operator):
    bl_idname = "assetlib.synchronize"
    bl_options = {"REGISTER", "UNDO"}
    bl_label = 'Synchronize'
    bl_description = 'Synchronize Action Lib to Local Directory'

    clean : BoolProperty(default=False)
    only_new : BoolProperty(default=False)
    only_recent : BoolProperty(default=False)
    name: StringProperty()
    all: BoolProperty(default=False)

    def execute(self, context: Context) -> Set[str]:

        print('Not yet Implemented, have to be replace by Bundle instead')
        return {'FINISHED'}

        prefs = get_addon_prefs()
        print('Synchronize')
        if self.all:
            libs = prefs.libraries
        else:
            libs = [prefs.libraries.get(self.name)]

        for lib in libs:            
            if self.clean and Path(lib.path_local).exists():
                pass
                print('To check first')
                #shutil.rmtree(path_local)

            if not lib.path_local:
                continue

            synchronize(
                src=lib.path,
                dst=lib.path_local,
                only_new=self.only_new,
                only_recent=self.only_recent
            )

        return {'FINISHED'}

classes = (
    ASSETLIB_OT_play_preview,
    ASSETLIB_OT_open_blend,
    ASSETLIB_OT_set_paths,
    ASSETLIB_OT_synchronize,
    ASSETLIB_OT_add_user_library,
    ASSETLIB_OT_remove_user_library,
    ASSETLIB_OT_diff,
    ASSETLIB_OT_generate_previews,
    ASSETLIB_OT_bundle_library,
    ASSETLIB_OT_clear_asset,
    ASSETLIB_OT_edit_data,
    #ASSETLIB_OT_conform_library,
    ASSETLIB_OT_reload_addon
)

def register():
    #bpy.types.UserAssetLibrary.is_env = False

    for cls in classes:
        bpy.utils.register_class(cls)

def unregister():
    for cls in reversed(classes):
        bpy.utils.unregister_class(cls)