from typing import Set, Dict

import HABApp
from HABApp.openhab.connection_handler.func_async import async_set_habapp_metadata, async_create_item, \
    async_remove_item, async_create_channel_link, async_get_items, \
    async_remove_channel_link, async_remove_metadata, async_set_metadata, async_get_item_with_habapp_meta
from HABApp.openhab.definitions.rest.habapp_data import HABAppThingPluginData, load_habapp_meta
from ._log import log_item as log
from .cfg_validator import UserItem


def _filter_items(i):
    if not i.get('editable', False):
        return False

    if 'HABApp' not in i.setdefault('metadata', {}):
        return False

    load_habapp_meta(i)
    if not isinstance(i['metadata']['HABApp'], HABAppThingPluginData):
        return False
    return True


async def cleanup_items(keep_items: Set[str]):
    all_items = await async_get_items(include_habapp_meta=True)

    to_delete: Dict[str, HABAppThingPluginData] = {}
    for cfg in filter(_filter_items, all_items):
        name = cfg['name']
        if name not in keep_items:
            to_delete[name] = cfg['metadata']['HABApp']

    if not to_delete:
        return None

    for item, data in to_delete.items():
        assert isinstance(data, HABAppThingPluginData)
        await _remove_item(item, data)


async def _remove_item(item: str, data: HABAppThingPluginData):
    # remove created link
    if data.created_link is not None:
        log.debug(f'Removing link from {data.created_link} to {item}')
        await async_remove_channel_link(data.created_link, item)

    # remove created metadata
    for ns in data.created_ns:
        log.debug(f'Removing metadata {ns} from {item}')
        await async_remove_metadata(item, ns)

    # finally remove the item
    log.info(f'Removing item {item}')
    await async_remove_item(item)


async def create_item(item: UserItem, test: bool) -> bool:

    if test:
        _txt = str(item)
        if _txt.startswith('UserItem'):
            _txt = _txt[4:]
        log.info(f'Would create {_txt}')
        return False

    name = item.name

    try:
        existing_ok = True
        existing_item = await async_get_item_with_habapp_meta(name)
        habapp_data = existing_item['metadata']['HABApp']

        # we only modify items we created
        if not isinstance(habapp_data, HABAppThingPluginData):
            log.warning(f'Skipping item {name} because it does already exist and was not created by the plugin!')
            return False

        # check if the item properties are already correct
        for k, v in item.get_oh_cfg().items():
            if v != existing_item.get(k, ''):
                existing_ok = False

    except HABApp.openhab.exceptions.ItemNotFoundError:
        existing_ok = True
        existing_item = None
        habapp_data = HABAppThingPluginData()

    # Update/Create item definition
    if existing_item is None or not existing_ok:
        log.info(f'{"Creating" if existing_item is None else "Updating"} item: {item.type} {name} "{item.label}"')
        tmp = item.get_oh_cfg()
        tmp['item_type'] = tmp.pop('type')
        if not await async_create_item(**tmp):
            log.error(f'Item operation failed for {tmp}!')
            return False
        await async_set_habapp_metadata(name, habapp_data)
    else:
        log.debug(f'Item {name} is already correct!')

    # check create link
    if item.link != habapp_data.created_link:
        # remove existing
        if habapp_data.created_link:
            log.debug(f'Removing link from {habapp_data.created_link} to {name}')
            await async_remove_channel_link(habapp_data.created_link, name)

        # create new link
        log.debug(f'Creating link from {item.link} to {item.name}')
        if not await async_create_channel_link(item.link, name):
            log.error(f'Creating link from {item.link} to {name} failed!')
            await _remove_item(name, habapp_data)
            return False

        # save that we created a link
        habapp_data.created_link = item.link
        await async_set_habapp_metadata(name, habapp_data)
    else:
        log.debug(f'Link to {name} is already correct')

    # check create metadata
    if item.metadata or habapp_data.created_ns:
        # remove obsolete
        for ns in set(habapp_data.created_ns) - set(item.metadata.keys()):
            log.debug(f'Removing metadata {ns} from {name}')
            await async_remove_metadata(name, ns)

        # create new
        for ns, meta_cfg in item.metadata.items():
            m_val = meta_cfg['value']
            m_config = meta_cfg['config']
            log.debug(f'Adding metadata {ns} to {name}: {m_val} {m_config}')
            if await async_set_metadata(name, ns, m_val, m_config):
                habapp_data.created_ns.append(ns)
            else:
                log.error(f'Creating metadata for {name} failed!')
                await _remove_item(name, habapp_data)
                return False

        # save that we created metadata
        habapp_data.created_ns = list(item.metadata.keys())
        await async_set_habapp_metadata(name, habapp_data)

    return True
