# Copyright (C) 2024 Thomas Hoppe (h0bB1T). All rights reserved.
#
# Unauthorized copying of this file via any medium is strictly prohibited.
# Proprietary and confidential.

import os

from dataclasses import dataclass
from typing import Dict, List, Callable
from uuid import uuid4

from ..awp.invoke_extract_assets import update_asset_database
from ..constants import asset_cache_file, asset_cache_dir, asset_cache_base_path
from ..utils.io import read_json, unified_path
from ..utils.dev import inf, err, log_exception


def asset_preview_image(hash: str) -> str:
    """
    Return full path to preview, which may be a placeholder image.
    """
    if hash:
        full_name = os.path.join(asset_cache_dir, f'{hash}.png')
        if os.path.exists(full_name):
            return full_name
        
    os.path.join(os.path.dirname(__file__), '..', 'data', 'images', 'no-preview.png')


@dataclass
class AssetEntry:
    """
    Contains all information for a single asset, stored in
    cache and parsed from .blends in library folders.
    """
    blend: str
    name: str
    type: str
    library: str
    catalog: str
    description: str
    tags: List[str]
    hash: str
    uuid: str # Dynamically set during load or scan, NOT saved, but unique for each asset, used in UI.

    def category(self):
        """
        Category is either first tag entry or 'Other'.
        """
        return self.tags[0] if self.tags else 'Other'
    
    
    def toc(self):
        """
        Used for per TOC listing, currently first letter of name.
        """
        return self.name[0]

 
class AssetInfoEntry:
    """
    Stores all information about a specific blend file.
    """
    def __init__(self):
        self.last_mod = 0 # Last file mod time.
        # Name, GroupType, Catalog, Tags.
        self.assets = [] # type: List[AssetEntry]


    def from_dict(self, data: Dict):
        """
        Restore data from json.
        """
        self.last_mod = data['last_mod']
        for e in data['assets']:
            self.assets.append(
                AssetEntry(
                    e['blend'],
                    e['name'], 
                    e['type'], 
                    e['library'],
                    e['catalog'], 
                    e['description'], 
                    e['tags'], 
                    e['hash'],
                    str(uuid4())
                )
            )


class AssetInfoCache:
    """
    Tracks catalog & tag info from assets in a separate json,
    as they are not accessible (yet) in Blenders API.
    """
    def __init__(self):
        self.__cache = {} # type: Dict[str, AssetInfoEntry]
        self.__active = {} # type: Dict[str, AssetInfoEntry]
        self.__use_caching = True # Deactive temporarily.
        self.__load()


    def __load(self):
        self.__cache.clear()

        # Restore info from json.
        inf(f'Load asset cache info from: {asset_cache_file}')
        js = read_json(asset_cache_file)
        for e in js.get('libs', list()):
            try:
                entry = AssetInfoEntry()
                entry.from_dict(e['entry'])
                self.__cache[e['name']] = entry
            except Exception as e:
                log_exception(e, context_msg='Failed to load asset cache info')


    def re_init(self):
        """
        Called before updating the cache with single blends. In the cache
        files may be files that are not in the repo paths. Remove them.
        """
        self.__active.clear()


    def update_info(self, lib: str, blend_file: str, rescan_from_outdated_file: bool):
        """
        This is called on startup and every time the user triggers a refresh through ResourceListsRegistry.update_nodes()
        for every file in the library folders. Scans the file itself if it is not already cached or the
        file modification time is different to the one stored in cache. Otherwise, the info from the cache (__cache) is used.
        After iterating over all files, __active contains all assets in the library.
        """
        inf('Update asset cache info')
        blend_file = unified_path(blend_file)
        if os.path.exists(blend_file):
            inf(f'  Handle: {blend_file}')
            last_mod = os.stat(blend_file).st_mtime

            # Check if update for this file is necessary.
            if self.__use_caching:
                if blend_file in self.__cache:
                    entry = self.__cache[blend_file]
                else:
                    entry = AssetInfoEntry()
                    self.__cache[blend_file] = entry
            else:
                entry = AssetInfoEntry()

            self.__active[blend_file] = entry
            if last_mod - entry.last_mod > 0.01 and rescan_from_outdated_file:
                inf('    .. scan')

                # Clear outdated information.
                entry.last_mod = last_mod
                entry.assets.clear()

                # Run external instance to update information.
                update_asset_database(blend_file, asset_cache_base_path, lib)

                # Reimport from external source.
                try:
                    js = read_json(asset_cache_file)
                    for e in js.get('libs', list()):
                        if e['name'] == blend_file:
                            current_entry = e['entry'] # type: dict[str, any]
                            entry.from_dict(current_entry)
                            break # Corrent entry found, we're done.                       
                except Exception as e:
                    log_exception(e, context_msg='Failed to reload info from cache')


    def assets(self) -> List[AssetEntry]:
        """
        Returns all assets from all .blends, sorted by name.
        """
        r = [] # type: List[AssetEntry]
        for ae in self.__active.values():
            r.extend(ae.assets)
        return sorted(r, key=lambda a: a.name)


    def assets_by(self, flt: Callable[[AssetEntry], bool]) -> List[AssetEntry]:
        """
        Filters all assets in cache by given filter and returns the list.
        """
        return filter(flt, self.assets())


    @staticmethod
    def get() -> 'AssetInfoCache':
        global _asset_info_cache_instance
        return _asset_info_cache_instance


_asset_info_cache_instance = AssetInfoCache()   
