import asyncio
import logging

from relay import misc
from relay.config import RelayConfig
from relay.database import RelayDatabase


async def handle_relay(request, actor, data, software):
    if data.objectid in request.app.cache.objects:
        logging.info(f"already relayed {data.objectid}")
        return

    logging.info(f"Relaying post from {data.actorid}")

    message = misc.Message.new_announce(
        host=request.app.config.host, object=data.objectid
    )

    logging.debug(f">> relay: {message}")

    inboxes = misc.distill_inboxes(data.objectid)
    futures = [misc.request(inbox, data=message) for inbox in inboxes]

    asyncio.ensure_future(asyncio.gather(*futures))
    request.app.cache.objects[data.objectid] = message.id


async def handle_forward(request, actor, data, software):
    if data.id in request.app.cache.objects:
        logging.info(f"already forwarded {data.id}")
        return

    message = misc.Message.new_announce(host=request.app.config.host, object=data)

    logging.info(f"Forwarding post from {actor.id}")
    logging.debug(f">> Relay {data}")

    inboxes = misc.distill_inboxes(data.id)
    futures = [misc.request(inbox, data=message) for inbox in inboxes]

    asyncio.ensure_future(asyncio.gather(*futures))
    request.app.cache.objects[data.id] = message.id


async def handle_follow(request, actor, data, software):
    config: RelayConfig = request.app.config
    database: RelayDatabase = request.app.database

    # If the following host is not whitelisted, we want to enqueue the request for review.
    # This means saving off the two parameters we need later to issue an appropriate acceptance.
    if config.whitelist_enabled and not config.is_whitelisted(data.domain):
        database.add_request(actor.id, actor.shared_inbox, data.id)
        database.save()
        return

    if not database.add_inbox(actor.shared_inbox, data.id):
        database.set_followid(actor.id, data.id)
        database.save()

    await misc.request(
        actor.shared_inbox,
        misc.Message.new_response(
            host=request.app.config.host,
            actor=actor.id,
            followid=data.id,
            accept=True
        ),
    )

    await misc.request(
        actor.shared_inbox,
        misc.Message.new_follow(
            host=request.app.config.host,
            actor=actor.id
        ),
    )


async def handle_undo(request, actor, data, software):
    # If the object is not a Follow, forward it
    if data["object"]["type"] != "Follow":
        return await handle_forward(request, actor, data, software)

    if not request.app.database.del_inbox(actor.domain, data.id):
        return

    request.app.database.save()

    message = misc.Message.new_unfollow(
        host=request.app.config.host, actor=actor.id, follow=data
    )

    await misc.request(actor.shared_inbox, message)


async def handle_dont(request, actor, data, software):
    """Handle something by ... not handling it."""

    logging.info(f"Disregarding {data!r}")


processors = {
    "Announce": handle_relay,
    "Create": handle_relay,
    "Delete": handle_forward,
    "Follow": handle_follow,
    "Undo": handle_undo,
    "Update": handle_forward,
}


async def run_processor(request, actor, data, software):
    if data.type not in processors:
        return

    logging.info(f'{request.id}: New "{data.type}" from actor: {actor.id}')
    logging.debug(f"{request.id}: {data!r}")

    env = dict(data=data, actor=actor, software=software)
    try:
        return await processors.get(data.type, handle_dont)(request, actor, data, software)

    except:
        logging.exception(f"{request.id}] {env!r}")
