import math

import arguments
import commands
import utils
import youtubedl
from state import client, players


async def queue_or_play(message):
    await ensure_joined(message)
    if not command_allowed(message):
        return

    if message.guild.id not in players:
        players[message.guild.id] = youtubedl.QueuedPlayer()

    tokens = commands.tokenize(message.content)
    parser = arguments.ArgumentParser(
        tokens[0], "queue a song, list the queue, or resume playback"
    )
    parser.add_argument("query", nargs="?", help="yt-dlp URL or query to get song")
    parser.add_argument(
        "-v",
        "--volume",
        default=50,
        type=lambda v: arguments.range_type(v, min=0, max=150),
        help="the volume level (0 - 150) for the specified song",
    )
    parser.add_argument(
        "-i",
        "--remove-index",
        type=int,
        help="remove a queued song by index",
    )
    parser.add_argument(
        "-m",
        "--remove-multiple",
        action="store_true",
        help="continue removing queued after finding a match",
    )
    parser.add_argument(
        "-c",
        "--clear",
        action="store_true",
        help="remove all queued songs",
    )
    parser.add_argument(
        "--now",
        action="store_true",
        help="play the specified song immediately",
    )
    parser.add_argument(
        "-t",
        "--remove-title",
        help="remove queued songs by title",
    )
    parser.add_argument(
        "-q",
        "--remove-queuer",
        type=int,
        help="remove queued songs by queuer",
    )
    parser.add_argument(
        "-d",
        "--duration",
        action="store_true",
        help="print duration of queued songs",
    )
    parser.add_argument(
        "-p",
        "--page",
        type=int,
        default=1,
        help="print the specified page of the queue",
    )
    if not (args := await parser.parse_args(message, tokens)):
        return

    if args.duration:
        queued_songs = players[message.guild.id].queue
        formatted_duration = utils.format_duration(
            sum(
                [
                    queued.player.duration if queued.player.duration else 0
                    for queued in queued_songs
                ]
            )
        )
        await utils.reply(
            message,
            f"queue is **{formatted_duration or '0 seconds'}** long (**{len(queued_songs)}** queued)",
        )
    elif args.clear:
        players[message.guild.id].queue.clear()
        await utils.add_check_reaction(message)
        return
    elif i := args.remove_index:
        if i <= 0 or i > len(players[message.guild.id].queue):
            await utils.reply(message, "invalid index!")
            return

        queued = players[message.guild.id].queue[i - 1]
        del players[message.guild.id].queue[i - 1]
        await utils.reply(message, f"**X** {queued.format()}")
    elif args.remove_title or args.remove_queuer:
        targets = []
        for queued in players[message.guild.id].queue:
            if t := args.remove_title:
                if t in queued.player.title:
                    targets.append(queued)
                    continue
            if q := args.remove_queuer:
                if q == queued.queuer:
                    targets.append(queued)
        if not args.remove_multiple:
            targets = targets[:1]

        for target in targets:
            players[message.guild.id].queue.remove(target)
        await utils.reply(
            message,
            f"removed **{len(targets)}** queued {'song' if len(targets) == 1 else 'songs'}",
        )
    elif query := args.query:
        try:
            async with message.channel.typing():
                player = await youtubedl.YTDLSource.from_url(
                    query, loop=client.loop, stream=True
                )
                player.volume = float(args.volume) / 100.0
        except Exception as e:
            await utils.reply(
                message,
                f"**unable to queue {query}:** {e}",
            )
            return

        queued = youtubedl.QueuedSong(player, message.author.id)

        if args.now:
            players[message.guild.id].queue_add_front(queued)
        else:
            players[message.guild.id].queue_add(queued)

        if (
            not message.guild.voice_client.is_playing()
            and not message.guild.voice_client.is_paused()
        ):
            play_next(message)
        elif args.now:
            message.guild.voice_client.stop()
        else:
            await utils.reply(
                message,
                f"**{len(players[message.guild.id].queue)}.** {queued.format()}",
            )
    else:
        if tokens[0].lower() == "play":
            message.guild.voice_client.resume()
            await utils.reply(
                message,
                "resumed!",
            )
        else:
            args.page = max(
                min(args.page, math.ceil(len(players[message.guild.id].queue) / 10)), 1
            )
            queue_list = lambda: "\n".join(
                [
                    f"**{i + 1}.** {queued.format(with_queuer=True, hide_preview=True)}"
                    for i, queued in list(enumerate(players[message.guild.id].queue))[
                        (args.page - 1) * 10 : args.page * 10
                    ]
                ]
            )
            currently_playing = (
                lambda: f"**0.** {'(paused) ' if message.guild.voice_client.is_paused() else ''} {players[message.guild.id].current.format(with_queuer=True)}"
            )
            if (
                not players[message.guild.id].queue
                and not message.guild.voice_client.source
            ):
                await utils.reply(
                    message,
                    "nothing is playing or queued!",
                )
            elif not players[message.guild.id].queue:
                await utils.reply(message, currently_playing())
            elif not message.guild.voice_client.source:
                await utils.reply(
                    message,
                    queue_list(),
                )
            else:
                await utils.reply(
                    message,
                    currently_playing() + "\n" + queue_list(),
                )


async def skip(message):
    if not command_allowed(message):
        return

    if not players[message.guild.id].queue:
        message.guild.voice_client.stop()
        await utils.reply(
            message,
            "the queue is empty now!",
        )
    else:
        message.guild.voice_client.stop()
        await utils.add_check_reaction(message)
        if (
            not message.guild.voice_client.is_playing()
            and not message.guild.voice_client.is_paused()
        ):
            play_next(message)


async def join(message):
    if message.guild.voice_client:
        return await message.guild.voice_client.move_to(message.channel)

    await message.channel.connect()


async def leave(message):
    if not command_allowed(message):
        return

    await message.guild.voice_client.disconnect()


async def resume(message):
    if not command_allowed(message):
        return

    if message.guild.voice_client.is_paused():
        message.guild.voice_client.resume()
        await utils.add_check_reaction(message)
    else:
        await utils.reply(
            message,
            "nothing is paused!",
        )


async def pause(message):
    if not command_allowed(message):
        return

    if message.guild.voice_client.is_playing():
        message.guild.voice_client.pause()
        await utils.add_check_reaction(message)
    else:
        await utils.reply(
            message,
            "nothing is playing!",
        )


async def volume(message):
    if not command_allowed(message):
        return

    if not message.guild.voice_client:
        return

    tokens = commands.tokenize(message.content)
    parser = arguments.ArgumentParser(tokens[0], "set the current volume level")
    parser.add_argument(
        "volume",
        nargs="?",
        type=lambda v: arguments.range_type(v, min=0, max=150),
        help="the volume level (0 - 150)",
    )
    if not (args := await parser.parse_args(message, tokens)):
        return

    if not message.guild.voice_client.source:
        await utils.reply(
            message,
            f"nothing is playing!",
        )
        return

    if args.volume is None:
        await utils.reply(
            message,
            f"{int(message.guild.voice_client.source.volume * 100)}",
        )
    else:
        message.guild.voice_client.source.volume = float(args.volume) / 100.0
        await utils.add_check_reaction(message)


def play_after_callback(e, message, once):
    if e:
        print(f"player error: {e}")
    if not once:
        play_next(message)


def play_next(message, once=False):
    message.guild.voice_client.stop()
    if players[message.guild.id].queue:
        queued = players[message.guild.id].queue_pop()
        try:
            message.guild.voice_client.play(
                queued.player, after=lambda e: play_after_callback(e, message, once)
            )
        except Exception as e:
            client.loop.create_task(
                message.channel.send(f"error while trying to play: `{e}`")
            )
            return
        client.loop.create_task(message.channel.send(f"**0.** {queued.format()}"))


async def ensure_joined(message):
    if message.guild.voice_client is None:
        if message.author.voice:
            await message.author.voice.channel.connect()
        else:
            await utils.reply(message, "You are not connected to a voice channel.")


def command_allowed(message):
    if not message.author.voice or not message.guild.voice_client:
        return False
    return message.author.voice.channel.id == message.guild.voice_client.channel.id