import logging
from datetime import datetime
from typing import Optional

import discord
from discord.ext import commands

from tuxbot.cogs.Mod.functions.converters import (
    RuleConverter,
    RuleIDConverter,
    BotMessageConverter,
    ReasonConverter,
)
from tuxbot.cogs.Mod.functions.exceptions import (
    RuleTooLongException,
    UnknownRuleException,
    NonMessageException,
    NonBotMessageException,
    ReasonTooLongException,
)
from tuxbot.cogs.Mod.functions.utils import (
    save_lang,
    get_server_rules,
    format_rule,
    get_most_recent_server_rules,
    paginate_server_rules,
    get_mute_role,
    create_mute_role,
)
from tuxbot.cogs.Mod.models.rules import Rule
from tuxbot.core.utils import checks
from tuxbot.core.bot import Tux

from tuxbot.core.i18n import (
    Translator,
    find_locale,
    get_locale_name,
    list_locales,
)
from tuxbot.core.utils.functions.extra import (
    group_extra,
    ContextPlus,
    command_extra,
)

log = logging.getLogger("tuxbot.cogs.Mod")
_ = Translator("Mod", __file__)


class Mod(commands.Cog):
    def __init__(self, bot: Tux):
        self.bot = bot

    async def cog_command_error(self, ctx: ContextPlus, error):
        if isinstance(
            error,
            (
                RuleTooLongException,
                UnknownRuleException,
                NonMessageException,
                NonBotMessageException,
                ReasonTooLongException,
            ),
        ):
            return await ctx.send(_(str(error), ctx, self.bot.config))

    # =========================================================================
    # =========================================================================

    @group_extra(name="lang", aliases=["locale", "langue"], deletable=True)
    @commands.guild_only()
    @checks.is_admin()
    async def _lang(self, ctx: ContextPlus):
        """Manage lang settings."""

    @_lang.command(name="set", aliases=["define", "choice"])
    async def _lang_set(self, ctx: ContextPlus, lang: str):
        try:
            await save_lang(self.bot, ctx, find_locale(lang.lower()))
            await ctx.send(
                _(
                    "Locale changed to {lang} successfully",
                    ctx,
                    self.bot.config,
                ).format(lang=f"`{get_locale_name(lang).lower()}`")
            )
        except NotImplementedError:
            await self._lang_list(ctx)

    @_lang.command(name="list", aliases=["liste", "all", "view"])
    async def _lang_list(self, ctx: ContextPlus):
        e = discord.Embed(
            title=_("List of available locales: ", ctx, self.bot.config),
            description=list_locales(),
            color=0x36393E,
        )

        await ctx.send(embed=e)

    # =========================================================================

    @group_extra(
        name="rule",
        aliases=["rules", "regle", "regles"],
        deletable=False,
        invoke_without_command=True,
    )
    @commands.guild_only()
    async def _rule(
        self,
        ctx: ContextPlus,
        rule: RuleIDConverter,
        members: commands.Greedy[discord.Member],
    ):
        rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule)

        message = _(
            "{}please read the following rule: \n{}", ctx, self.bot.config
        )
        authors = ""

        for member in members:
            if member in ctx.message.mentions:
                authors += f"{member.name}#{member.discriminator}, "
            else:
                authors += f"{member.mention}, "

        await ctx.send(message.format(authors, format_rule(rule_row)))

    @_rule.command(name="list", aliases=["show", "all"])
    async def _rule_list(self, ctx: ContextPlus):
        rules = await get_server_rules(ctx.guild.id)

        if not rules:
            return await ctx.send(
                _("No rules found for this server", ctx, self.bot.config)
            )

        embed = discord.Embed(
            title=_("Rules for {}", ctx, self.bot.config).format(
                ctx.guild.name
            ),
            color=discord.Color.blue(),
        )
        embed.set_footer(
            text=_("Latest change: {}", ctx, self.bot.config).format(
                get_most_recent_server_rules(rules).updated_at.ctime()
            )
        )

        pages = paginate_server_rules(rules)

        if len(pages) == 1:
            embed.description = pages[0]

            await ctx.send(embed=embed)
        else:
            for i, page in enumerate(pages):
                embed.title = _(
                    "Rules for {} ({}/{})", ctx, self.bot.config
                ).format(ctx.guild.name, str(i + 1), str(len(pages)))
                embed.description = page

                await ctx.send(embed=embed)

    @checks.is_admin()
    @_rule.command(name="add")
    async def _rule_add(self, ctx: ContextPlus, *, rule: RuleConverter):
        rule_row = await Rule()
        rule_row.server_id = ctx.guild.id
        rule_row.author_id = ctx.message.author.id

        rule_row.rule_id = (
            len(await get_server_rules(ctx.guild.id)) + 1  # type: ignore
        )
        rule_row.content = str(rule)  # type: ignore

        await rule_row.save()

        await ctx.send(
            _("Following rule added: \n{}", ctx, self.bot.config).format(
                format_rule(rule_row)
            )
        )

    @checks.is_admin()
    @_rule.command(name="edit")
    async def _rule_edit(
        self,
        ctx: ContextPlus,
        rule: RuleIDConverter,
        *,
        content: RuleConverter,
    ):
        # noinspection PyTypeChecker
        rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule)

        rule_row.content = str(content)  # type: ignore
        rule_row.updated_at = datetime.now()  # type: ignore

        await rule_row.save()

        await ctx.send(
            _("Following rule updated: \n{}", ctx, self.bot.config).format(
                format_rule(rule_row)
            )
        )

    @checks.is_admin()
    @_rule.command(name="delete")
    async def _rule_delete(
        self,
        ctx: ContextPlus,
        rule: RuleIDConverter,
    ):
        # noinspection PyTypeChecker
        rule_row = await Rule.get(server_id=ctx.guild.id, rule_id=rule)

        await rule_row.delete()

        await ctx.send(
            _("Following rule deleted: \n{}", ctx, self.bot.config).format(
                format_rule(rule_row)
            )
        )

    @checks.is_admin()
    @_rule.command(name="update")
    async def _rule_update(
        self,
        ctx: ContextPlus,
        message: BotMessageConverter,
    ):
        rules = await get_server_rules(ctx.guild.id)

        if not rules:
            return await ctx.send(
                _("No rules found for this server", ctx, self.bot.config)
            )

        embed = discord.Embed(
            title=_("Rules for {}", ctx, self.bot.config).format(
                ctx.guild.name
            ),
            color=discord.Color.blue(),
        )
        embed.set_footer(
            text=_("Latest change: {}", ctx, self.bot.config).format(
                get_most_recent_server_rules(rules).updated_at.ctime()
            )
        )

        pages = paginate_server_rules(rules)

        # noinspection PyTypeChecker
        to_edit: discord.Message = message

        if len(pages) == 1:
            embed.description = pages[0]

            await to_edit.edit(content="", embed=embed)
        else:
            for i, page in enumerate(pages):
                embed.title = _(
                    "Rules for {} ({}/{})", ctx, self.bot.config
                ).format(ctx.guild.name, str(i + 1), str(len(pages)))
                embed.description = page

                await to_edit.edit(content="", embed=embed)

    # =========================================================================

    @group_extra(
        name="mute",
        deletable=True,
        invoke_without_command=True,
    )
    @commands.guild_only()
    @checks.is_admin()
    async def _mute(
        self,
        ctx: ContextPlus,
        members: commands.Greedy[discord.Member],
        *,
        reason: Optional[ReasonConverter],
    ):
        if not members:
            return await ctx.send(_("Missing members", ctx, self.bot.config))

        role_row = await get_mute_role(ctx.guild.id)

        if role_row is None:
            return await ctx.send(
                _(
                    "No mute role has been specified for this guild",
                    ctx,
                    self.bot.config,
                )
            )

        for member in members:
            await member.add_roles(
                discord.Object(id=int(role_row.role_id)), reason=reason
            )

        await ctx.send("\N{THUMBS UP SIGN}")

    @_mute.command(name="show", aliases=["role"])
    async def _mute_show(
        self,
        ctx: ContextPlus,
    ):
        role_row = await get_mute_role(ctx.guild.id)

        if (
            role_row is None
            or (role := ctx.guild.get_role(int(role_row.role_id))) is None
        ):
            return await ctx.send(
                _(
                    "No mute role has been specified for this guild",
                    ctx,
                    self.bot.config,
                )
            )

        muted_members = [m for m in ctx.guild.members if role in m.roles]

        e = discord.Embed(
            title=f"Role: {role.name} (ID: {role.id})", color=role.color
        )
        e.add_field(name="Total mute:", value=len(muted_members))

        await ctx.send(embed=e)

    @_mute.command(name="set", aliases=["define"])
    async def _mute_set(self, ctx: ContextPlus, role: discord.Role):
        role_row = await get_mute_role(ctx.guild.id)

        if role_row is None:
            await create_mute_role(ctx.guild.id, role.id)
        else:
            role_row.role_id = role.id  # type: ignore
            await role_row.save()

        await ctx.send(
            _("Mute role successfully defined", ctx, self.bot.config)
        )

    # =========================================================================

    @command_extra(
        name="tempmute",
        deletable=True,
    )
    @commands.guild_only()
    @checks.is_admin()
    async def _tempmute(
        self,
        ctx: ContextPlus,
        time,
        members: discord.Member,
        *,
        reason: Optional[ReasonConverter],
    ):
        _, _, _, _ = ctx, time, members, reason

    # =========================================================================

    @command_extra(
        name="unmute",
        deletable=True,
    )
    @commands.guild_only()
    @checks.is_admin()
    async def _unmute(
        self,
        ctx: ContextPlus,
        members: commands.Greedy[discord.Member],
        *,
        reason: Optional[ReasonConverter],
    ):
        if not members:
            return await ctx.send(_("Missing members", ctx, self.bot.config))

        role_row = await get_mute_role(ctx.guild.id)

        if role_row is None:
            return await ctx.send(
                _(
                    "No mute role has been specified for this guild",
                    ctx,
                    self.bot.config,
                )
            )

        for member in members:
            await member.remove_roles(
                discord.Object(id=int(role_row.role_id)), reason=reason
            )

        await ctx.send("\N{THUMBS UP SIGN}")