# pylint: disable=cyclic-import
from datetime import datetime, timedelta
from typing import Union

from discord import Color, Embed, Guild, Interaction, Member, Message, Role, User
from redbot.core import commands
from redbot.core.bot import Red
from redbot.core.utils.chat_formatting import bold, box, error, humanize_timedelta, inline, warning

from ..models.moderation import Moderation
from ..models.partials import PartialUser
from ..models.type import Type
from .config import config
from .utils import get_bool_emoji, get_pagesize_str


async def message_factory(
    bot: Red,
    color: Color,
    guild: Guild,
    reason: str,
    moderation_type: Type,
    moderator: Union[Member, User] | None = None,
    duration: timedelta | None = None,
    response: Message | None = None,
    case: bool = True,
) -> Embed:
    """This function creates a message from set parameters, meant for contacting the moderated user.

    Args:
        bot (Red): The bot instance.
        color (Color): The color of the embed.
        guild (Guild): The guild the moderation occurred in.
        reason (str): The reason for the moderation.
        moderation_type (Type): The type of moderation.
        moderator (Union[Member, User], optional): The moderator who performed the moderation. Defaults to None.
        duration (timedelta, optional): The duration of the moderation. Defaults to None.
        response (Message, optional): The response message. Defaults to None.
        case (bool, optional): Whether the message is for a moderation case. Defaults to True.


    Returns:
        embed: The message embed.
    """
    if response is not None and not moderation_type.removes_from_guild:
        guild_name = f"[{guild.name}]({response.jump_url})"
    else:
        guild_name = guild.name

    if duration:
        embed_duration = f" for {humanize_timedelta(timedelta=duration)}"
    else:
        embed_duration = ""

    embed = Embed(
        title=str.title(moderation_type.verb),
        description=f"You have {moderation_type.embed_desc}{moderation_type.verb}{embed_duration} in {guild_name}.",
        color=color,
        timestamp=datetime.now(),
    )

    show_moderator = await config.custom("types", guild.id, moderation_type.key).show_moderator()
    if show_moderator is None:
        show_moderator = await config.guild(guild).show_moderator()

    if show_moderator and moderator is not None:
        embed.add_field(
            name="Moderator", value=f"`{moderator.name} ({moderator.id})`", inline=False
        )

    embed.add_field(name="Reason", value=f"`{reason}`", inline=False)

    if guild.icon.url is not None:
        embed.set_author(name=guild.name, icon_url=guild.icon.url)
    else:
        embed.set_author(name=guild.name)

    if case:
        embed.set_footer(
            text=f"Case #{await Moderation.get_next_case_number(bot=bot, guild_id=guild.id):,}",
            icon_url="attachment://arrow.png",
        )

    return embed

async def resolve_factory(moderation: Moderation, reason: str) -> Embed:
    """This function creates a resolved embed from set parameters, meant for contacting the moderated user.

    Args:
        moderation (aurora.models.Moderation): The moderation object.
        reason (str): The reason for resolving the moderation.
    Returns: `discord.Embed`
    """

    embed = Embed(
        title=str.title(moderation.type.name) + " Resolved",
        description=f"Your {moderation.type.name} in {moderation.guild.name} has been resolved.",
        color=await moderation.bot.get_embed_color(moderation.guild.channels[0]),
        timestamp=datetime.now(),
    )

    embed.add_field(name="Reason", value=f"`{reason}`", inline=False)

    if moderation.guild.icon.url is not None:
        embed.set_author(name=moderation.guild.name, icon_url=moderation.guild.icon.url)
    else:
        embed.set_author(name=moderation.guild.name)
    embed.set_footer(
        text=f"Case #{moderation.id:,}",
        icon_url="attachment://arrow.png",
    )

    return embed

async def log_factory(
    ctx: commands.Context, moderation: Moderation, resolved: bool = False
) -> Embed:
    """This function creates a log embed from set parameters, meant for moderation logging.

    Args:
        ctx (commands.Context): The ctx object.
        moderation (aurora.models.Moderation): The moderation object.
        resolved (bool, optional): Whether the case is resolved or not. Defaults to False.
    """
    target = await moderation.get_target()
    moderator = await moderation.get_moderator()
    if resolved:
        embed = Embed(
            title=f"📕 Case #{moderation.id:,} Resolved",
            color=await ctx.bot.get_embed_color(ctx.channel),
        )

        resolved_by = await moderation.get_resolved_by()
        embed.description = f"**Type:** {str.title(moderation.type.string)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"

        if moderation.duration is not None:
            duration_embed = (
                f"{humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.end_timestamp}:R>"
                if not moderation.expired
                else str(humanize_timedelta(timedelta=moderation.duration))
            )
            embed.description = (
                embed.description
                + f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}"
            )

        if moderation.metadata.items():
            for key, value in moderation.metadata.items():
                embed.description += f"\n**{key.title()}:** {value}"

        embed.add_field(name="Reason", value=box(moderation.reason), inline=False)

        embed.add_field(
            name="Resolve Reason",
            value=f"Resolved by `{resolved_by.name}` ({resolved_by.id}) for:\n"
            + box(moderation.resolve_reason),
            inline=False,
        )
    else:
        embed = Embed(
            title=f"📕 Case #{moderation.id:,}",
            color=await ctx.bot.get_embed_color(ctx.channel),
        )
        embed.description = f"**Type:** {str.title(moderation.type.string)}\n**Target:** {target.name} ({target.id})\n**Moderator:** {moderator.name} ({moderator.id})\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"

        if moderation.duration:
            embed.description = (
                embed.description
                + f"\n**Duration:** {humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.unix_timestamp}:R>"
            )

        if moderation.metadata.items():
            for key, value in moderation.metadata.items():
                embed.description += f"\n**{key.title()}:** {value}"

        embed.add_field(name="Reason", value=box(moderation.reason), inline=False)
    return embed


async def case_factory(interaction: Interaction, moderation: Moderation) -> Embed:
    """This function creates a case embed from set parameters.

    Args:
        interaction (discord.Interaction): The interaction object.
        moderation (aurora.models.Moderation): The moderation object.
    """
    target = await moderation.get_target()
    moderator = await moderation.get_moderator()

    embed = Embed(
        title=f"📕 Case #{moderation.id:,}",
        color=await interaction.client.get_embed_color(interaction.channel),
    )
    embed.description = f"**Type:** {str.title(moderation.type.string)}\n**Target:** `{target.name}` ({target.id})\n**Moderator:** `{moderator.name}` ({moderator.id})\n**Resolved:** {moderation.resolved}\n**Timestamp:** <t:{moderation.unix_timestamp}> | <t:{moderation.unix_timestamp}:R>"

    if moderation.duration:
        duration_embed = (
            f"{humanize_timedelta(timedelta=moderation.duration)} | <t:{moderation.unix_timestamp}:R>"
            if moderation.expired is False
            else str(humanize_timedelta(timedelta=moderation.duration))
        )
        embed.description += f"\n**Duration:** {duration_embed}\n**Expired:** {moderation.expired}"

    embed.description += (
        f"\n**Changes:** {len(moderation.changes) - 1}"
        if moderation.changes
        else "\n**Changes:** 0"
    )

    if moderation.role_id:
        role = await moderation.get_role()
        embed.description += f"\n**Role:** {role.name}"

    if moderation.metadata:
        if moderation.metadata.get("imported_from"):
            embed.description += (
                f"\n**Imported From:** {moderation.metadata['imported_from']}"
            )
            moderation.metadata.pop("imported_from")
        if moderation.metadata.get("imported_timestamp"):
            embed.description += (
                f"\n**Imported Timestamp:** <t:{moderation.metadata['imported_timestamp']}> | <t:{moderation.metadata['imported_timestamp']}:R>"
            )
            moderation.metadata.pop("imported_timestamp")
        if moderation.metadata.items():
            for key, value in moderation.metadata.items():
                embed.description += f"\n**{key.title()}:** {value}"

    embed.add_field(name="Reason", value=box(moderation.reason), inline=False)

    if moderation.resolved:
        resolved_user = await moderation.get_resolved_by()
        embed.add_field(
            name="Resolve Reason",
            value=f"Resolved by `{resolved_user.name}` ({resolved_user.id}) for:\n{box(moderation.resolve_reason)}",
            inline=False,
        )

    return embed


async def changes_factory(interaction: Interaction, moderation: Moderation) -> Embed:
    """This function creates a changes embed from set parameters.

    Args:
        interaction (discord.Interaction): The interaction object.
        moderation (aurora.models.Moderation): The moderation object.
    """
    embed = Embed(
        title=f"📕 Case #{moderation.id:,} Changes",
        color=await interaction.client.get_embed_color(interaction.channel),
    )

    memory_dict = {}

    if moderation.changes:
        for change in moderation.changes:
            if change.user_id not in memory_dict:
                memory_dict[str(change.user_id)] = await change.get_user()

            user: PartialUser = memory_dict[str(change.user_id)]

            timestamp = f"<t:{change.unix_timestamp}> | <t:{change.unix_timestamp}:R>"
            end_timestamp = f"<t:{change.unix_end_timestamp}> | <t:{change.unix_end_timestamp}:R>" if change.end_timestamp else None

            change_str = [
                f"{bold('User:')} {inline(user.name)} ({user.id})",
                f"{bold('Reason:')} {change.reason}" if change.reason else "",
                f"{bold('Duration:')} {humanize_timedelta(timedelta=change.duration)}" if change.duration else "",
                f"{bold('End Timestamp:')} {end_timestamp}" if end_timestamp else "",
                f"{bold('Timestamp')} {timestamp}",
            ]
            copy = change_str.copy()
            for string in change_str:
                if string == "":
                    copy.remove(string)

            embed.add_field(
                name=change.type.title(),
                value="\n".join(copy),
                inline=False,
            )

    else:
        embed.description = "*No changes have been made to this case.* 🙁"

    return embed


async def evidenceformat_factory(moderation: Moderation) -> str:
    """This function creates a codeblock in evidence format from set parameters.

    Args:
        interaction (discord.Interaction): The interaction object.
        moderation (aurora.models.Moderation): The moderation object.
    """
    target = await moderation.get_target()
    moderator = await moderation.get_moderator()

    content = f"Case: {moderation.id:,} ({str.title(moderation.type.string)})\nTarget: {target.name} ({target.id})\nModerator: {moderator.name} ({moderator.id})"

    if moderation.duration is not None:
        content += f"\nDuration: {humanize_timedelta(timedelta=moderation.duration)}"

    if moderation.role_id:
        role = await moderation.get_role()
        content += "\nRole: " + (role.name if role is not None else moderation.role_id)

    content += f"\nReason: {moderation.reason}"

    for key, value in moderation.metadata.items():
        content += f"\n{key.title()}: {value}"

    return box(content, "prolog")


########################################################################################################################
### Configuration Embeds                                                                                               #
########################################################################################################################


async def _config(ctx: commands.Context) -> Embed:
    """Generates the core embed for configuration menus to use."""
    e = Embed(title="Aurora Configuration Menu", color=await ctx.embed_color())
    e.set_thumbnail(url=ctx.bot.user.display_avatar.url)
    return e


async def overrides_embed(ctx: commands.Context) -> Embed:
    """Generates a configuration menu embed for a user's overrides."""

    override_settings = {
        "ephemeral": await config.user(ctx.author).history_ephemeral(),
        "inline": await config.user(ctx.author).history_inline(),
        "inline_pagesize": await config.user(ctx.author).history_inline_pagesize(),
        "pagesize": await config.user(ctx.author).history_pagesize(),
        "auto_evidenceformat": await config.user(ctx.author).auto_evidenceformat(),
    }

    override_str = [
        "- "
        + bold("Auto Evidence Format: ")
        + get_bool_emoji(override_settings["auto_evidenceformat"]),
        "- " + bold("Ephemeral: ") + get_bool_emoji(override_settings["ephemeral"]),
        "- " + bold("History Inline: ") + get_bool_emoji(override_settings["inline"]),
        "- "
        + bold("History Inline Pagesize: ")
        + get_pagesize_str(override_settings["inline_pagesize"]),
        "- "
        + bold("History Pagesize: ")
        + get_pagesize_str(override_settings["pagesize"]),
    ]
    override_str = "\n".join(override_str)

    e = await _config(ctx)
    e.title += ": User Overrides"
    e.description = (
        """
    Use the buttons below to manage your user overrides.
    These settings will override the relevant guild settings.\n
    """
        + override_str
    )
    return e


async def guild_embed(ctx: commands.Context) -> Embed:
    """Generates a configuration menu field value for a guild's settings."""

    guild_settings = {
        "show_moderator": await config.guild(ctx.guild).show_moderator(),
        "use_discord_permissions": await config.guild(
            ctx.guild
        ).use_discord_permissions(),
        "ignore_modlog": await config.guild(ctx.guild).ignore_modlog(),
        "ignore_other_bots": await config.guild(ctx.guild).ignore_other_bots(),
        "dm_users": await config.guild(ctx.guild).dm_users(),
        "log_channel": await config.guild(ctx.guild).log_channel(),
        "history_ephemeral": await config.guild(ctx.guild).history_ephemeral(),
        "history_inline": await config.guild(ctx.guild).history_inline(),
        "history_pagesize": await config.guild(ctx.guild).history_pagesize(),
        "history_inline_pagesize": await config.guild(
            ctx.guild
        ).history_inline_pagesize(),
        "auto_evidenceformat": await config.guild(ctx.guild).auto_evidenceformat(),
        "respect_hierarchy": await config.guild(ctx.guild).respect_hierarchy(),
    }

    channel = ctx.guild.get_channel(guild_settings["log_channel"])
    if channel is None:
        channel = warning("Not Set")
    else:
        channel = channel.mention

    guild_str = [
        "- "
        + bold("Show Moderator: ")
        + get_bool_emoji(guild_settings["show_moderator"]),
        "- "
        + bold("Use Discord Permissions: ")
        + get_bool_emoji(guild_settings["use_discord_permissions"]),
        "- "
        + bold("Respect Hierarchy: ")
        + get_bool_emoji(guild_settings["respect_hierarchy"]),
        "- "
        + bold("Ignore Modlog: ")
        + get_bool_emoji(guild_settings["ignore_modlog"]),
        "- "
        + bold("Ignore Other Bots: ")
        + get_bool_emoji(guild_settings["ignore_other_bots"]),
        "- " + bold("DM Users: ") + get_bool_emoji(guild_settings["dm_users"]),
        "- "
        + bold("Auto Evidence Format: ")
        + get_bool_emoji(guild_settings["auto_evidenceformat"]),
        "- "
        + bold("Ephemeral: ")
        + get_bool_emoji(guild_settings["history_ephemeral"]),
        "- "
        + bold("History Inline: ")
        + get_bool_emoji(guild_settings["history_inline"]),
        "- "
        + bold("History Pagesize: ")
        + get_pagesize_str(guild_settings["history_pagesize"]),
        "- "
        + bold("History Inline Pagesize: ")
        + get_pagesize_str(guild_settings["history_inline_pagesize"]),
        "- " + bold("Log Channel: ") + channel,
    ]
    guild_str = "\n".join(guild_str)

    e = await _config(ctx)
    e.title += ": Server Configuration"
    e.description = (
        """
    Use the buttons below to manage Aurora's server configuration.\n
    """
        + guild_str
    )
    return e


async def addrole_embed(ctx: commands.Context) -> Embed:
    """Generates a configuration menu field value for a guild's addrole whitelist."""

    roles = []
    async with config.guild(ctx.guild).addrole_whitelist() as whitelist:
        for role in whitelist:
            evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)")
            if isinstance(evalulated_role, Role):
                roles.append({
                    "id": evalulated_role.id,
                    "mention": evalulated_role.mention,
                    "position": evalulated_role.position
                })
            else:
                roles.append({
                    "id": role,
                    "mention": error(f"`{role}` (Not Found)"),
                    "position": 0
                })

    if roles:
        roles = sorted(roles, key=lambda x: x["position"], reverse=True)
        roles = [role["mention"] for role in roles]
        whitelist_str = "\n".join(roles)
    else:
        whitelist_str = warning("No roles are on the addrole whitelist!")

    e = await _config(ctx)
    e.title += ": Addrole Whitelist"
    e.description = (
        "Use the select menu below to manage this guild's addrole whitelist."
    )

    if len(whitelist_str) > 4000 and len(whitelist_str) < 5000:
        lines = whitelist_str.split("\n")
        chunks = []
        chunk = ""
        for line in lines:
            if len(chunk) + len(line) > 1024:
                chunks.append(chunk)
                chunk = line
            else:
                chunk += "\n" + line if chunk else line
        chunks.append(chunk)

        for chunk in chunks:
            e.add_field(name="", value=chunk)
    else:
        e.description += "\n\n" + whitelist_str

    return e


async def immune_embed(ctx: commands.Context) -> Embed:
    """Generates a configuration menu embed for a guild's immune roles."""

    roles = []
    async with config.guild(ctx.guild).immune_roles() as immune_roles:
        for role in immune_roles:
            evalulated_role = ctx.guild.get_role(role) or error(f"`{role}` (Not Found)")
            if isinstance(evalulated_role, Role):
                roles.append({
                    "id": evalulated_role.id,
                    "mention": evalulated_role.mention,
                    "position": evalulated_role.position
                })
            else:
                roles.append({
                    "id": role,
                    "mention": error(f"`{role}` (Not Found)"),
                    "position": 0
                })

    if roles:
        roles = sorted(roles, key=lambda x: x["position"], reverse=True)
        roles = [role["mention"] for role in roles]
        immune_str = "\n".join(roles)
    else:
        immune_str = warning("No roles are set as immune roles!")

    e = await _config(ctx)
    e.title += ": Immune Roles"
    e.description = "Use the select menu below to manage this guild's immune roles."

    if len(immune_str) > 4000 and len(immune_str) < 5000:
        lines = immune_str.split("\n")
        chunks = []
        chunk = ""
        for line in lines:
            if len(chunk) + len(line) > 1024:
                chunks.append(chunk)
                chunk = line
            else:
                chunk += "\n" + line if chunk else line
        chunks.append(chunk)

        for chunk in chunks:
            e.add_field(name="", value=chunk)
    else:
        e.description += "\n\n" + immune_str

    return e

async def type_embed(ctx: commands.Context, moderation_type = Type) -> Embed:
    """Generates a configuration menu field value for a guild's settings."""

    type_settings = {
        "show_in_history": await config.custom("types", ctx.guild.id, moderation_type.key).show_in_history(),
        "show_moderator": await config.custom("types", ctx.guild.id, moderation_type.key).show_moderator(),
        "use_discord_permissions": await config.custom("types", ctx.guild.id, moderation_type.key).use_discord_permissions(),
        "dm_users": await config.custom("types", ctx.guild.id, moderation_type.key).dm_users(),
    }

    guild_str = [
        "- "
        + bold("Show in History: ")
        + get_bool_emoji(type_settings["show_in_history"]),
        "- "
        + bold("Show Moderator: ")
        + get_bool_emoji(type_settings["show_moderator"]),
        "- "
        + bold("Use Discord Permissions: ")
        + get_bool_emoji(type_settings["use_discord_permissions"]),
        "- "
        + bold("DM Users: ")
        + get_bool_emoji(type_settings["dm_users"]),
    ]
    guild_str = "\n".join(guild_str)

    e = await _config(ctx)
    e.title += f": {moderation_type.string.title()} Configuration"
    e.description = (
    f"""
    Use the buttons below to manage Aurora's configuration for the {bold(moderation_type.string)} moderation type.
    If an option has a question mark (\N{BLACK QUESTION MARK ORNAMENT}\N{VARIATION SELECTOR-16}) next to it, Aurora will default to the guild level setting instead.
    See `{ctx.prefix}aurora set guild` for more information.\n
    """
        + guild_str
    )
    return e
