import traceback

import aiohttp
import discord

from models.deepl_model import TranslationModel
from services.environment_service import EnvService


ALLOWED_GUILDS = EnvService.get_allowed_guilds()


def build_translation_embed(
    text,
    translated_text,
    translated_language,
    detected_language,
    requestor: discord.User,
):
    """Build an embed for the translation"""
    embed_description = (
        f"**Original Text:** \n\n{text}\n\n **Translated Text:** \n\n{translated_text}"
    )
    embed = discord.Embed(
        title=f"Translation from {detected_language} to {translated_language}",
        description=embed_description,
        color=0x311432,
    )
    embed.set_footer(
        text=f"Requested by {requestor.name}#{requestor.discriminator}",
        icon_url=requestor.avatar.url
        if requestor.avatar
        else requestor.default_avatar.url,
    )

    return embed


class TranslationService(discord.Cog, name="TranslationService"):
    """Cog containing translation commands and retrieval of translation services"""

    def __init__(
        self,
        bot,
        translation_model,
    ):
        super().__init__()
        self.bot = bot
        self.translation_model = translation_model
        # Make a mapping of all the country codes and their full country names:

    def build_supported_language_embed(self):
        """Build an embed for the translation"""
        embed = discord.Embed(
            title="Translator supported languages",
            color=0x311432,
        )
        # Add the list of supported languages in a nice format
        embed.add_field(
            name="Languages",
            value=", ".join(
                [f"{name}" for name in TranslationModel.get_all_country_names()]
            ),
            inline=False,
        )

        return embed

    async def translate_command(self, ctx, text, target_language, formality):
        """Command handler for the translation command"""
        await ctx.defer()
        # TODO Add pagination!

        if (
            target_language.lower().strip()
            not in TranslationModel.get_all_country_names(lower=True)
        ):
            await ctx.respond(
                f"The language {target_language} is not recognized or supported. Please use `/languages` to see the list of supported languages."
            )
            return

        try:
            (
                response,
                detected_language,
            ) = await self.translation_model.send_translate_request(
                text,
                TranslationModel.get_country_code_from_name(target_language),
                formality,
            )
        except aiohttp.ClientResponseError as e:
            await ctx.respond(f"There was an error with the DeepL API: {e.message}")
            return

        await ctx.respond(
            embed=build_translation_embed(
                text,
                response,
                target_language,
                TranslationModel.get_country_name_from_code(detected_language),
                ctx.user,
            )
        )

    async def translate_action(self, ctx: discord.ApplicationContext, message):
        await ctx.defer(ephemeral=True)
        # If the message is only an embed and there's no content, don't translate.
        if message.content == "" and len(message.embeds) > 0:
            await ctx.respond(
                "Cannot translate an embed.", ephemeral=True, delete_after=30
            )
            return

        if len(message.content) > 2000:
            await ctx.respond(
                "Message is too long to translate.", ephemeral=True, delete_after=30
            )
            return

        selection_message = await ctx.respond(
            "Select language", ephemeral=True, delete_after=60
        )
        await selection_message.edit(
            view=TranslateView(
                self.translation_model, message, selection_message, ctx.user
            )
        )

    async def languages_command(self, ctx):
        """Show all languages supported for translation"""
        await ctx.defer()
        await ctx.respond(embed=self.build_supported_language_embed())


class TranslateView(discord.ui.View):
    def __init__(self, translation_model, message, selection_message, requestor):
        super().__init__()
        self.language_long = None
        self.language = None
        self.translation_model = translation_model
        self.message = message
        self.selection_message = selection_message
        self.formality = None
        self.requestor = requestor

    @discord.ui.select(  # the decorator that lets you specify the properties of the select menu
        placeholder="Language",  # the placeholder text that will be displayed if nothing is selected
        min_values=1,  # the minimum number of values that must be selected by the users
        max_values=1,  # the maximum number of values that can be selected by the users
        options=[  # the list of options from which users can choose, a required field
            discord.SelectOption(
                label=name,
            )
            for name in TranslationModel.get_all_country_names()
        ],
    )
    async def select_callback(
        self, select, interaction
    ):  # the function called when the user is done selecting options
        try:
            self.language = TranslationModel.get_country_code_from_name(
                select.values[0]
            )
            self.language_long = select.values[0]
            await interaction.response.defer()
        except:
            traceback.print_exc()

    @discord.ui.select(
        placeholder="Formality (optional)",
        min_values=1,
        max_values=1,
        options=[
            discord.SelectOption(label="Prefer more", value="prefer_more"),
            discord.SelectOption(label="default", value="default"),
            discord.SelectOption(label="Prefer less", value="prefer_less"),
        ],
    )
    async def formality_callback(self, select, interaction):
        try:
            self.formality = select.values[0]
            await interaction.response.defer()
        except aiohttp.ClientResponseError as e:
            await interaction.response.send_message(
                f"There was an error with the DeepL API: {e.message}",
                ephemeral=True,
                delete_after=15,
            )
            return
        except Exception as e:
            await interaction.response.send_message(
                f"There was an error: {e}", ephemeral=True, delete_after=15
            )
            return

    # A button "Translate"
    @discord.ui.button(label="Translate", style=discord.ButtonStyle.green)
    async def button_callback(self, button, interaction):
        if not self.language or not self.language_long:
            await interaction.response.send_message(
                "Please select a language first.", ephemeral=True, delete_after=15
            )
            return
        try:
            (
                response,
                detected_language,
            ) = await self.translation_model.send_translate_request(
                self.message.content,
                self.language,
                self.formality,
            )
            await self.message.reply(
                mention_author=False,
                embed=build_translation_embed(
                    self.message.content,
                    response,
                    self.language_long,
                    TranslationModel.get_country_name_from_code(detected_language),
                    self.requestor,
                ),
            )
            await self.selection_message.delete()
        except aiohttp.ClientResponseError as e:
            await interaction.response.send_message(
                f"There was an error with the DeepL API: {e.message}",
                ephemeral=True,
                delete_after=15,
            )
            return
        except discord.errors.HTTPException as e:
            if e.code == 50035:
                await interaction.response.send_message(
                    "Message was too long to translate.",
                    ephemeral=True,
                    delete_after=15,
                )
                return
        except Exception as e:
            await interaction.response.send_message(
                f"There was an error: {e}", ephemeral=True, delete_after=15
            )
            traceback.print_exc()
            return
