"""
Fake model used for testing and verification.
"""
import asyncio
import itertools
import pytricia
from preseem import NetworkMetadataEntity, NetworkMetadataReference
from . import NetworkMetadataLowercaseIndex


class FakeNetworkMetadataModel(object):

    def __init__(self, cbk=None, refs_by_type=None, indexes=None):
        self._cbk = cbk  # optional callback for updates
        self.refs_by_type = refs_by_type
        self.refs = {}  # public: current references
        self.nms = {}  # public: current entities
        self.refs_last_updated = {}
        self.pt4 = pytricia.PyTricia(32)
        self._index = {x.type: {} for x in indexes or []}
        self._index_cfg = {x.type: {} for x in indexes or []}
        for index in indexes or []:
            self._index[index.type][index.attribute] = {}
            self._index_cfg[index.type][index.attribute] = index

    def close(self):
        pass

    def is_loading(self):
        return False

    async def list_nm(self, resolve_refs=False):
        return list(self.nms.values())

    async def set_nm(self, entity):
        assert isinstance(entity, NetworkMetadataEntity)
        self.nms[entity.network_prefix] = entity
        if ':' not in entity.network_prefix:
            try:
                self.pt4[entity.network_prefix] = entity
            except Exception as err:
                logging.warning('Error setting nm entity %s: %s', entity.network_prefix,
                                err)
                del self.nm_syn[nm.network_prefix]
        if self._cbk:
            await self._cbk(entity, deleted=False)

    async def del_nm(self, entity):
        assert isinstance(entity, NetworkMetadataEntity)
        try:
            del self.nms[entity.network_prefix]
            try:
                del self.pt4[entity.network_prefix]
            except KeyError:
                pass
            if self._cbk:
                await self._cbk(entity, deleted=True)
        except AttributeError:
            pass

    def get_ref_index(self, reftype, attr, val=None):
        return self._index[reftype][attr].get(val)

    async def list_refs(self):
        if self.refs_by_type:
            return list(
                itertools.chain.from_iterable([x.values() for x in self.refs.values()]))
        else:
            return list(self.refs.values())

    async def set_ref(self, ref):
        assert isinstance(ref, NetworkMetadataReference)
        if self.refs_by_type:
            refs = self.refs.get(ref.type)
            if refs is None:
                refs = self.refs[ref.type] = {}
            cur = refs.get(ref.value)
            refs[ref.value] = ref
            index = self._index.get(ref.type)
            if index is not None:
                for ia, ir in index.items():
                    is_lower = isinstance(self._index_cfg[ref.type][ia],
                                          NetworkMetadataLowercaseIndex)
                    if ia == '_LOWER':
                        refs = ir.get(None)
                        if refs is None:
                            refs = ir[None] = {}
                        refs[ref.value.lower()] = ref
                        continue
                    if cur:
                        # Remove the old ref from any indexes
                        attr = cur.attributes.get(ia)
                        if attr:
                            if is_lower:
                                attr = attr.lower()
                            refs = ir.get(attr)
                            if refs:
                                refs.pop(ref.value, None)
                    attr = ref.attributes.get(ia)
                    if attr:
                        if is_lower:
                            attr = attr.lower()
                        refs = ir.get(attr)
                        if refs is None:
                            refs = ir[attr] = {}
                        refs[ref.value] = ref
        else:
            self.refs[(ref.type, ref.value)] = ref
        self.refs_last_updated[ref.type] = asyncio.get_event_loop().time()
        if self._cbk:
            await self._cbk(ref, deleted=False)

    async def del_ref(self, ref):
        assert isinstance(ref, NetworkMetadataReference)
        try:
            if self.refs_by_type:
                ref = self.refs[ref.type].pop(ref.value)
                if not self.refs[ref.type]:
                    del self.refs[ref.type]
                index = self._index.get(ref.type)
                if index is not None:
                    for ia, ir in index.items():
                        is_lower = isinstance(self._index_cfg[ref.type][ia],
                                              NetworkMetadataLowercaseIndex)
                        if ia == '_LOWER':
                            refs = ir.get(None)
                            if refs:
                                refs.pop(ref.value.lower(), None)
                            continue
                        attr = ref.attributes.get(ia)
                        if attr:
                            if is_lower:
                                attr = attr.lower()
                            refs = ir.get(attr)
                            if refs:
                                refs.pop(ref.value, None)
            else:
                del self.refs[(ref.type, ref.value)]
            self.refs_last_updated[ref.type] = asyncio.get_event_loop().time()
            if self._cbk:
                await self._cbk(ref, deleted=True)
        except AttributeError:
            pass

    async def update(self):
        pass
