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=None):

        self.file_cache = file_cache
        
        self.catalog = None
        self.author = None
        self.description = None
        self.tags = None
        self.type = None
        self.name = None
        self._metadata = None

        if data:
            self.set_data(data)

    @property
    def filepath(self):
        return self.file_cache.filepath

    @property
    def library_id(self):
        return self.file_cache.library_id

    @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 unique_name(self):
        return (self.filepath / self.name).as_posix()

    def set_data(self, data):
        catalog = data['catalog']
        if isinstance(catalog, (list, tuple)):
            catalog = '/'.join(catalog)

        self.catalog = 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', {})

    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 __repr__(self):
        return f'AssetCache(name={self.name}, catalog={self.catalog})'

    def __eq__(self, other):
        return self.to_dict() == other.to_dict()


class AssetsCache:
    def __init__(self, file_cache):
        
        self.file_cache = file_cache
        self._data = []

    def add(self, asset_cache_data, **kargs):
        asset_cache = AssetCache(self.file_cache, {**asset_cache_data, **kargs})
        self._data.append(asset_cache)

        return asset_cache

    def remove(self, asset_cache):
        if isinstance(asset_cache, str):
            asset_cache = self.get(asset_cache)

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

    def __getitem__(self, key):
        if isinstance(key, str):
            return self.to_dict()[key]
        else:
            return self._data[key]

    def to_dict(self):
        return {a.name: a for a in self}

    def get(self, name):
        return next((a for a in self if a.name == name), None)

    def __repr__(self):
        return f'AssetsCache({list(self)})'


class FileCache:
    def __init__(self, library_cache, data=None):
        
        self.library_cache = library_cache

        self.filepath = None
        self.modified = None
        self.assets = AssetsCache(self)

        if data:
            self.set_data(data)

    @property
    def library_id(self):
        return self.library_cache.library_id

    def set_data(self, data):

        if 'filepath' in data:
            self.filepath = Path(data['filepath'])

        self.modified = data.get('modified', time.time_ns())

        if data.get('type') == 'FILE':
            self.assets.add(data)

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

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

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

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

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


class AssetCacheDiff:
    def __init__(self, library_cache, asset_cache, operation):

        self.library_cache = library_cache
        #self.filepath = data['filepath']
        self.operation = operation
        self.asset_cache = asset_cache


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

        self.filepath = filepath
        self._data = []

        self.compare(old_cache, new_cache)

    def add(self, asset_cache_datas, operation):
        if not isinstance(asset_cache_datas, (list, tuple)):
            asset_cache_datas = [asset_cache_datas]

        new_asset_diffs = []
        for cache_data in asset_cache_datas:
            new_asset_diffs.append(AssetCacheDiff(self, cache_data, operation))

        self._data += new_asset_diffs

        return new_asset_diffs
    
    def compare(self, old_cache, new_cache):
        if old_cache is None or new_cache is None:
            print('Cannot Compare cache with None')

        cache_dict = {a.unique_name : a for a in old_cache.asset_caches}
        new_cache_dict = {a.unique_name : a for a in new_cache.asset_caches}

        assets_added = self.add([v for k, v  in new_cache_dict.items() if k not in cache_dict], 'ADD')
        assets_removed = self.add([v for k, v  in cache_dict.items() if k not in new_cache_dict], 'REMOVED')
        assets_modified = self.add([v for k, v in cache_dict.items() if v not in assets_removed and v!= new_cache_dict[k]], 'MODIFIED')

        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')
        
        if len(self) == 0:
            print('No change in the library')

        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 __getitem__(self, key):
        return self._data[key]

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

    def __repr__(self):
        return f'LibraryCacheDiff(operations={[o for o in self][:2]}...)'


class LibraryCache:
    def __init__(self, filepath):
        
        self.filepath = Path(filepath)
        self._data = []

    @classmethod
    def from_library(cls, library):   
        filepath = library.library_path / f"blender_assets.{library.id}.json"
        return cls(filepath)

    @property
    def filename(self):
        return self.filepath.name

    @property
    def library_id(self):
        return self.filepath.stem.split('.')[-1]

    #@property
    #def filepath(self):
    #    """Get the filepath of the library json file relative to the library"""
    #    return self.directory / self.filename
    
    def catalogs(self):
        return set(a.catalog for a in self.asset_caches)

    @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, tmp=False):
        filepath = self.filepath
        if tmp:
            filepath = self.tmp_filepath

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

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

        self._data.append(file_cache)

        return file_cache

    def add_asset_cache(self, asset_cache_data, filepath=None):
        if filepath is None:
            filepath = asset_cache_data['filepath']

        file_cache = self.get(filepath)
        if not file_cache:
            file_cache = self.add()

        file_cache.assets.add(asset_cache_data)

    # 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=None):
        """Compare the library cache with it current state and return the cache differential"""

        old_cache = self.read()

        if new_cache is None:
            new_cache = self

        return  LibraryCacheDiff(old_cache, new_cache)

    def update(self, cache_diff):
        #Update the cache with the operations
        for asset_cache_diff in cache_diff:
            file_cache = self.get(asset_cache_diff.filepath)
            if not asset_cache:
                print(f'Filepath {asset_cache_diff.filepath} not in {self}' )
                continue

            asset_cache = file_cache.get(asset_cache_diff.name)

            if not asset_cache:
                print(f'Asset {asset_cache_diff.name} not in file_cache {file_cache}' )
                continue

            if asset_cache_diff.operation == 'REMOVE':
                file_cache.assets.remove(asset_cache_diff.name)

            elif asset_cache_diff.operation in ('MODIFY', 'ADD'):
                asset_cache.set_data(asset_cache_diff.asset_cache.to_dict())
        
        return self

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

    def __iter__(self):
        return iter(self._data)
    
    def __getitem__(self, key):
        if isinstance(key, str):
            return self.to_dict()[key]
        else:
            return self._data[key]

    def to_dict(self):
        return {a.filepath: a for a in self}

    def get(self, filepath):
        return next((a for a in self if a.filepath == filepath), None)

    def __repr__(self):
        return f'LibraryCache(library_id={self.library_id})'

