import asyncio
import datetime
import logging

import discord
from dictdiffer import diff

from . import apis
from . import config
from . import embeds

ONE_MINUTE = 60
LAUNCHING_SOON_DELTA = datetime.timedelta(minutes=config.NOTIF_TASK_LAUNCH_DELTA)


def get_embed_dict_differences(embed1: dict, embed2: dict) -> list:
    """Finds any differences between 2 embed dicts, returning the names of the keys /
    fields that are different. Does not find additions or deletions, just changes.

    Args:
        embed1: A dict of a discord Embed object.
        embed2: A dict of a discord Embed object.

    Returns:
        A List of strings that are the names of the changed keys / fields.

    """
    changes = []

    for difference in list(diff(embed1, embed2)):

        # The first index is the type of diff. We are looking for changes.
        if difference[0] == "change":
            if difference[1] == "image.url":
                # Ignore image url changes.
                continue

            # The second index ([1]) is the key, or in the case of fields, it is a list
            # like: ['fields', 0, 'value'].
            # Here we check it is a fields value that has changed, otherwise ignore it.
            if (
                isinstance(difference[1], list)
                and difference[1][0] == "fields"
                and difference[1][2] == "value"
            ):
                # diff[1][1] is the fields index in the embed dict.
                changes.append(embed1["fields"][difference[1][1]]["name"])

            else:
                changes.append(difference[1])

    return changes


async def _check_and_send_notifs(client: discord.Client) -> None:
    """Checks what notification messages need to be sent, and sends them.

    Updates database values if they need updating.

    Args:
        client : The Discord client to use to send messages.

    """
    next_launch_dict = await apis.spacex.get_launch_dict()

    # If the API is misbehaving, don't do anything, don't risk sending incorrect data
    if next_launch_dict == {}:
        return

    # Shortened to save space, ls = launching soon, li = launch information
    ls_notif_sent, old_li_embed_dict = client.ds.get_notification_task_vars()
    new_li_embed = await embeds.create_launch_info_embed(next_launch_dict)
    new_li_embed_dict = new_li_embed.to_dict()

    # This is the embed that will be saved to the notification task store
    embed_dict_to_save = old_li_embed_dict

    # Send out a launch information embed if it has changed from the previous one
    if new_li_embed_dict != old_li_embed_dict:
        logging.info("Launch info changed, sending notifications")

        # Get changes between old and new launch information embeds
        changes = get_embed_dict_differences(new_li_embed_dict, old_li_embed_dict)
        logging.info(f"Found changes: {changes}")

        # Deal with changes
        if len(changes) > 1:
            changed_text = f"Changed: {changes[0]} + {len(changes) - 1} more"
        elif len(changes) == 0:
            # It can be 0 if we use reset nts and the li embed dict is set to {}
            # The changes will be listed as "add" instead of "change" by dictdiffer
            changed_text = "Changed: New Embed"
        else:
            changed_text = f"Changed: {changes[0]}"

        new_li_embed.set_footer(text=changed_text)
        await client.send_all_subscribed(new_li_embed)

        ls_notif_sent = False
        embed_dict_to_save = new_li_embed_dict
    try:
        launch_timestamp = int(next_launch_dict["launch_date_unix"])
    except ValueError:
        # Doesn't have a date, don't trigger notifications
        launch_timestamp = 0

    current_time = datetime.datetime.utcnow()
    curr_time_plus_delta = (current_time + LAUNCHING_SOON_DELTA).timestamp()

    # Send out a launching soon notification if these criteria are met:
    # If the launch time is within the next NOTIF_TASK_LAUNCH_DELTA, and if the
    # launch_timestamp is not in the past, and we haven't already sent the notif
    if (
        curr_time_plus_delta >= launch_timestamp >= current_time.timestamp()
        and ls_notif_sent is False
    ):
        logging.info("Launch is soon, sending out notifications")
        launching_soon_embed = await embeds.create_launching_soon_embed(
            next_launch_dict
        )
        await client.send_all_subscribed(launching_soon_embed, True)
        ls_notif_sent = True

    # Save any changed data to db
    client.ds.set_notification_task_vars(ls_notif_sent, embed_dict_to_save)
    client.ds.save()


async def notification_task(client: discord.Client) -> None:
    """An async task to send out launching soon & launch info notifications."""
    await client.wait_until_ready()
    logging.info("Starting")

    while not client.is_closed():
        await _check_and_send_notifs(client)
        await asyncio.sleep(ONE_MINUTE * config.NOTIF_TASK_API_INTERVAL)