
import bpy
from pathlib import Path
from asset_library.common.file_utils import read_file, write_file
from copy import deepcopy
import time
from itertools import groupby


class AssetCache:
    def __init__(self, file_cache, data):

        self.file_cache = file_cache
        
        self._data = data

        self.catalog = data['catalog']
        self.author = data.get('author', '')
        self.description = data.get('description', '')
        self.tags = data.get('tags', [])
        self.type = data.get('type')
        self.name = data['name']
        self._metadata = data.get('metadata', {})
    
    @property
    def filepath(self):
        return self.file_cache.filepath

    @property
    def metadata(self):
        metadata = {
            '.library_id': self.library.id,
            '.filepath': self.filepath
        }

        metadata.update(self.metadata)

        return metadata

    @property
    def norm_name(self):
        return self.name.replace(' ', '_').lower()

    def to_dict(self):
        return dict(
            catalog=self.catalog,
            author=self.author,
            metadata=self.metadata,
            description=self.description,
            tags=self.tags,
            type=self.type,
            name=self.name
        )

    def __str__(self):
        return f'AssetCache(name={self.name}, type={self.type}, catalog={self.catalog})'


class FileCache:
    def __init__(self, library_cache, data):
        
        self.library_cache = library_cache
        self.filepath = data['filepath']
        self.modified = data.get('modified', time.time_ns())

        self._data = []

        for asset_cache_data in data.get('assets', []):
            self.add(asset_cache_data)

    def add(self, asset_cache_data):
        asset_cache = AssetCache(self, asset_cache_data)
        self._data.append(asset_cache)

    def to_dict(self):
        return dict(
            filepath=self.filepath.as_posix(),
            modified=self.modified,
            library_id=self.library_cache.library.id,
            assets=[asset_cache.to_dict() for asset_cache in self]
        )

    def __iter__(self):
        return self._data.__iter__()

    def __getitem__(self, key):
        return self._data[key]

    def __str__(self):
        return f'FileCache(filepath={self.filepath})'


class AssetCacheDiff:
    def __init__(self, library_diff, asset_cache, operation):
        
        self.library_cache = library_cache
        self.filepath = data['filepath']
        self.operation = operation


class LibraryCacheDiff:
    def __init__(self, filepath=None):

        self.filepath = filepath
        self._data = []    

    def add(self, asset_diff):
        asset_diff = AssetCacheDiff(self, asset_diff)
        self._data.append(asset_cache_diff)

    def set(self, asset_diffs):
        for asset_diff in asset_diffs:
            self.add(asset_diff)
    
    def read(self):
        print(f'Read cache from {self.filepath}')
  
        for asset_diff_data in read_file(self.filepath):
            self.add(asset_diff_data)

        return self

    def group_by(self, key):
        '''Return groups of file cache diff using the key provided'''
        data = list(self).sort(key=key)
        return groupby(data, key=key)

    def __iter__(self):
        return iter(self._data)
    
    def __len__(self):
        return len(self._data)

    def __getitem__(self, key):
        return self._data[key]


class LibraryCache:

    def __init__(self, directory, id):
        
        self.directory = directory
        self.id = id
        self._data = []

    @classmethod
    def from_library(cls, library):        
        return cls(library.library_path, library.id)

    @property
    def filename(self):
        return f"blender_assets.{self.id}.json"

    @property
    def filepath(self):
        """Get the filepath of the library json file relative to the library"""
        return self.directory / self.filename
    
    @property
    def asset_caches(self):
        '''Return an iterator to get all asset caches'''
        return (asset_cache for file_cache in self for asset_cache in file_cache)
    
    @property
    def tmp_filepath(self):
        return Path(bpy.app.tempdir) / self.filename

    def read(self):
        print(f'Read cache from {self.filepath}')
  
        for file_cache_data in read_file(self.filepath):
            self.add(file_cache_data)

        return self

    def write(self, temp=False):
        filepath = self.filepath
        if temp:
            filepath = self.tmp_filepath

        print(f'Write cache file to {filepath}')
        write_file(filepath, self._data)
        return filepath

    def add(self, file_cache_data):
        file_cache = FileCache(self, file_cache_data)

        self._data.append(file_cache)

    def unflatten_cache(self, cache):
        """ Return a new unflattten list of asset data
        grouped by filepath"""

        new_cache = []

        cache = deepcopy(cache)

        cache.sort(key=lambda x : x['filepath'])
        groups = groupby(cache, key=lambda x : x['filepath'])

        keys = ['filepath', 'modified', 'library_id']

        for _, asset_datas in groups:
            asset_datas = list(asset_datas)

            #print(asset_datas[0])

            asset_info = {k:asset_datas[0][k] for k in keys}
            asset_info['assets'] = [{k:v for k, v in a.items() if k not in keys+['operation']} for a in asset_datas]

            new_cache.append(asset_info)

        return new_cache

    def diff(self, new_cache):
        """Compare the library cache with it current state and return the cache differential"""

        cache = self.read()

        cache_dict = {f"{a['filepath']}/{a['name']}": a for a in cache.asset_caches}
        new_cache_dict = {f"{a['filepath']}/{a['name']}" : a for a in new_cache.asset_caches}

        assets_added = [AssetCacheDiff(v, 'ADD') for k, v  in new_cache.items() if k not in cache]
        assets_removed = [AssetCacheDiff(v, 'REMOVED') for k, v  in cache.items() if k not in new_cache]
        assets_modified = [AssetCacheDiff(v, 'MODIFIED') for k, v in cache.items() if v not in assets_removed and v!= new_cache[k]]

        if assets_added:
            print(f'{len(assets_added)} Assets Added \n{tuple(a.name for a in assets_added[:10])}...\n')
        if assets_removed:
            print(f'{len(assets_removed)} Assets Removed \n{tuple(a.name for a in assets_removed[:10])}...\n')
        if assets_modified:
            print(f'{len(assets_modified)} Assets Modified \n{tuple(a.name for a in assets_modified[:10])}...\n')
        
        cache_diff = LibraryCacheDiff()
        cache_diff.set(assets_added+assets_removed+assets_modified)
        
        if not len(LibraryCacheDiff):
            print('No change in the library')

        return cache_diff
    
    def __len__(self):
        return len(self._data)

    def __iter__(self):
        return iter(self._data)
    
    def __getitem__(self, key):
        return self._data[key]
    
    def __str__(self):
        return f'LibraryCache(library={self.library.name})'

print()

prefs = bpy.context.preferences.addons['asset_library'].preferences


library = prefs.env_libraries[0]
library_cache = LibraryCache.from_library(library).read()

data = library.library_type.fetch()
print(data)

print(library_cache[0][0])

#library_cache.diff(library.library_type.fetch())


#print(library_cache[0])
