import asyncio
import collections
import copy
import datetime
import json
import time
from random import choice

import discord
from redbot.core import Config, bank, commands
from redbot.core.bot import Red
from redbot.core.data_manager import bundled_data_path


class Gardener(commands.Cog):
    """Gardener class"""

    def __init__(self, user: discord.User, config: Config):
        super().__init__()
        self.user = user
        self.config = config
        self.badges = []
        self.points = 0
        self.products = {}
        self.current = {}

    def __str__(self):
        return (
            "Gardener named {}\n"
            "Badges: {}\n"
            "Points: {}\n"
            "Products: {}\n"
            "Current: {}".format(self.user, self.badges, self.points, self.products, self.current)
        )

    def __repr__(self):
        return "{} - {} - {} - {} - {}".format(
            self.user, self.badges, self.points, self.products, self.current
        )

    async def load_config(self):
        self.badges = await self.config.user(self.user).badges()
        self.points = await self.config.user(self.user).points()
        self.products = await self.config.user(self.user).products()
        self.current = await self.config.user(self.user).current()

    async def save_gardener(self):
        await self.config.user(self.user).badges.set(self.badges)
        await self.config.user(self.user).points.set(self.points)
        await self.config.user(self.user).products.set(self.products)
        await self.config.user(self.user).current.set(self.current)

    async def is_complete(self, now):

        message = None
        if self.current:
            then = self.current["timestamp"]
            health = self.current["health"]
            grow_time = self.current["time"]
            badge = self.current["badge"]
            reward = self.current["reward"]
            if (now - then) > grow_time:
                self.points += reward
                if badge not in self.badges:
                    self.badges.append(badge)
                message = (
                    "Your plant made it! "
                    "You are rewarded with the **{}** badge and you have received **{}** Thneeds.".format(
                        badge, reward
                    )
                )
            if health < 0:
                message = "Your plant died!"

        if message is not None:
            self.current = {}
            await self.save_gardener()
            await self.user.send(message)


async def _die_in(gardener, degradation):
    #
    # Calculating how much time in minutes remains until the plant's health hits 0
    #

    return int(gardener.current["health"] / degradation.degradation)


async def _grow_time(gardener):
    #
    # Calculating the remaining grow time for a plant
    #

    now = int(time.time())
    then = gardener.current["timestamp"]
    return (gardener.current["time"] - (now - then)) / 60


async def _send_message(channel, message):
    """Sendsa message"""

    em = discord.Embed(description=message, color=discord.Color.green())
    await channel.send(embed=em)


async def _withdraw_points(gardener: Gardener, amount):
    #
    # Substract points from the gardener
    #

    if (gardener.points - amount) < 0:
        return False
    else:
        gardener.points -= amount
        return True


class PlantTycoon(commands.Cog):
    """Grow your own plants! Be sure to take proper care of it."""

    def __init__(self, bot: Red, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.bot = bot
        self.config = Config.get_conf(self, identifier=80108971101168412199111111110)

        default_user = {"badges": [], "points": 0, "products": {}, "current": {}}

        self.config.register_user(**default_user)

        self.plants = None

        self.products = None

        self.defaults = {
            "points": {
                "buy": 5,
                "add_health": 5,
                "fertilize": 10,
                "pruning": 20,
                "pesticide": 25,
                "growing": 5,
                "damage": 25,
            },
            "timers": {"degradation": 1, "completion": 1, "notification": 5},
            "degradation": {"base_degradation": 1.5},
            "notification": {"max_health": 50},
        }

        self.badges = {
            "badges": {
                "Flower Power": {},
                "Fruit Brute": {},
                "Sporadic": {},
                "Odd-pod": {},
                "Greenfingers": {},
                "Nobel Peas Prize": {},
                "Annualsary": {},
            }
        }

        self.notifications = {
            "messages": [
                "The soil seems dry, maybe you could give your plant some water?",
                "Your plant seems a bit droopy. I would give it some fertilizer if I were you.",
                "Your plant seems a bit too overgrown. You should probably trim it a bit.",
            ]
        }

        #
        # Starting loops
        #

        self.completion_task = bot.loop.create_task(self.check_completion_loop())
        # self.degradation_task = bot.loop.create_task(self.check_degradation())
        self.notification_task = bot.loop.create_task(self.send_notification())

        #
        # Loading bank
        #

        # self.bank = bot.get_cog('Economy').bank

    async def _load_plants_products(self):
        plant_path = bundled_data_path(self) / "plants.json"
        product_path = bundled_data_path(self) / "products.json"
        with plant_path.open() as json_data:
            self.plants = json.load(json_data)

        await self._load_event_seeds()

        with product_path.open() as json_data:
            self.products = json.load(json_data)

        for product in self.products:
            print("PlantTycoon: Loaded {}".format(product))

    async def _load_event_seeds(self):
        self.plants["all_plants"] = copy.deepcopy(self.plants["plants"])
        plant_options = self.plants["all_plants"]

        d = datetime.date.today()
        month = d.month
        if month == 1:
            plant_options.append(self.plants["event"]["January"])
        elif month == 2:
            plant_options.append(self.plants["event"]["February"])
        elif month == 3:
            plant_options.append(self.plants["event"]["March"])
        elif month == 4:
            plant_options.append(self.plants["event"]["April"])
        elif month == 10:
            plant_options.append(self.plants["event"]["October"])
        elif month == 11:
            plant_options.append(self.plants["event"]["November"])
        elif month == 12:
            plant_options.append(self.plants["event"]["December"])

    async def _gardener(self, user: discord.User) -> Gardener:

        #
        # This function returns a Gardener object for the user
        #

        g = Gardener(user, self.config)
        await g.load_config()
        return g

    async def _degradation(self, gardener: Gardener):

        #
        # Calculating the rate of degradation per check_completion_loop() cycle.
        #
        if self.products is None:
            await self._load_plants_products()

        modifiers = sum(
            [
                self.products[product]["modifier"]
                for product in gardener.products
                if gardener.products[product] > 0
            ]
        )

        degradation = (
            100
            / (gardener.current["time"] / 60)
            * (self.defaults["degradation"]["base_degradation"] + gardener.current["degradation"])
        ) + modifiers

        d = collections.namedtuple("degradation", "degradation time modifiers")

        return d(degradation=degradation, time=gardener.current["time"], modifiers=modifiers)

    # async def _get_member(self, user_id):
    #
    #     #
    #     # Return a member object
    #     #
    #
    #     return discord.User(id=user_id)  # I made it a string just to be sure
    #
    # async def _send_notification(self, user_id, message):
    #
    #     #
    #     # Sends a Direct Message to the gardener
    #     #
    #
    #     member = await self._get_member(user_id)
    #     em = discord.Embed(description=message, color=discord.Color.green())
    #     await self.bot.send_message(member, embed=em)

    async def _add_health(self, channel, gardener: Gardener, product, product_category):

        #
        # The function to add health
        #
        if self.products is None:
            await self._load_plants_products()
        product = product.lower()
        product_category = product_category.lower()
        if product in self.products and self.products[product]["category"] == product_category:
            if product in gardener.products:
                if gardener.products[product] > 0:
                    gardener.current["health"] += self.products[product]["health"]
                    gardener.products[product] -= 1
                    if gardener.products[product] == 0:
                        del gardener.products[product.lower()]
                    if product_category == "water":
                        emoji = ":sweat_drops:"
                    elif product_category == "fertilizer":
                        emoji = ":poop:"
                    # elif product_category == "tool":
                    else:
                        emoji = ":scissors:"
                    message = "Your plant got some health back! {}".format(emoji)
                    if gardener.current["health"] > gardener.current["threshold"]:
                        gardener.current["health"] -= self.products[product]["damage"]
                        if product_category == "tool":
                            damage_msg = "You used {} too many times!".format(product)
                        else:
                            damage_msg = "You gave too much of {}.".format(product)
                        message = "{} Your plant lost some health. :wilted_rose:".format(
                            damage_msg
                        )
                    gardener.points += self.defaults["points"]["add_health"]
                    await gardener.save_gardener()
                else:
                    message = "You have no {}. Go buy some!".format(product)
            else:
                if product_category == "tool":
                    message = "You don't have a {}. Go buy one!".format(product)
                else:
                    message = "You have no {}. Go buy some!".format(product)
        else:
            message = "Are you sure you are using {}?".format(product_category)

        if product_category == "water":
            emcolor = discord.Color.blue()
        elif product_category == "fertilizer":
            emcolor = discord.Color.dark_gold()
        # elif product_category == "tool":
        else:
            emcolor = discord.Color.dark_grey()

        em = discord.Embed(description=message, color=emcolor)
        await channel.send(embed=em)

    @commands.group(name="gardening", autohelp=False)
    async def _gardening(self, ctx: commands.Context):
        """Gardening commands."""
        if ctx.invoked_subcommand is None:
            prefix = ctx.prefix

            title = "**Welcome to Plant Tycoon.**\n"
            description = """'Grow your own plant. Be sure to take proper care of yours.\n
            If it successfully grows, you get a reward.\n
            As you nurture your plant, you gain Thneeds which can be exchanged for credits.\n\n
            **Commands**\n\n
            ``{0}gardening seed``: Plant a seed inside the earth.\n
            ``{0}gardening profile``: Check your gardening profile.\n
            ``{0}gardening plants``: Look at the list of the available plants.\n
            ``{0}gardening plant``: Look at the details of a plant.\n
            ``{0}gardening state``: Check the state of your plant.\n
            ``{0}gardening buy``: Buy gardening supplies.\n
            ``{0}gardening convert``: Exchange Thneeds for credits.\n
            ``{0}shovel``: Shovel your plant out.\n
            ``{0}water``: Water your plant.\n
            ``{0}fertilize``: Fertilize the soil.\n
            ``{0}prune``: Prune your plant.\n"""

            em = discord.Embed(
                title=title, description=description.format(prefix), color=discord.Color.green(),
            )
            em.set_thumbnail(url="https://image.prntscr.com/image/AW7GuFIBSeyEgkR2W3SeiQ.png")
            em.set_footer(
                text="This cog was made by SnappyDragon18 and PaddoInWonderland. Inspired by The Lorax (2012)."
            )
            await ctx.send(embed=em)

    @commands.cooldown(1, 60 * 10, commands.BucketType.user)
    @_gardening.command(name="seed")
    async def _seed(self, ctx: commands.Context):
        """Plant a seed inside the earth."""
        if self.plants is None:
            await self._load_plants_products()
        author = ctx.author
        # server = context.message.server
        # if author.id not in self.gardeners:
        #     self.gardeners[author.id] = {}
        #     self.gardeners[author.id]['current'] = False
        #     self.gardeners[author.id]['points'] = 0
        #     self.gardeners[author.id]['badges'] = []
        #     self.gardeners[author.id]['products'] = {}
        gardener = await self._gardener(author)

        if not gardener.current:
            plant_options = self.plants["all_plants"]

            plant = choice(plant_options)
            plant["timestamp"] = int(time.time())
            plant["degrade_count"] = 0
            # index = len(self.plants["plants"]) - 1
            # del [self.plants["plants"][index]]
            message = (
                "During one of your many heroic adventures, you came across a mysterious bag that said "
                '"pick one". To your surprise it had all kinds of different seeds in them. '
                "And now that you're home, you want to plant it. "
                "You went to a local farmer to identify the seed, and the farmer "
                "said it was {} **{} ({})** seed.\n\n"
                "Take good care of your seed and water it frequently. "
                "Once it blooms, something nice might come from it. "
                "If it dies, however, you will get nothing.".format(
                    plant["article"], plant["name"], plant["rarity"]
                )
            )
            if "water" not in gardener.products:
                gardener.products["water"] = 0
            gardener.products["water"] += 5
            gardener.current = plant
            await gardener.save_gardener()

            em = discord.Embed(description=message, color=discord.Color.green())
        else:
            plant = gardener.current
            message = "You're already growing {} **{}**, silly.".format(
                plant["article"], plant["name"]
            )
            em = discord.Embed(description=message, color=discord.Color.green())

        await ctx.send(embed=em)

    @_gardening.command(name="profile")
    async def _profile(self, ctx: commands.Context, *, member: discord.Member = None):
        """Check your gardening profile."""
        if member is not None:
            author = member
        else:
            author = ctx.author

        gardener = await self._gardener(author)
        try:
            await self._apply_degradation(gardener)
        except discord.Forbidden:
            await ctx.send("ERROR\nYou blocked me, didn't you?")

        em = discord.Embed(color=discord.Color.green())  # , description='\a\n')
        avatar = author.avatar_url if author.avatar else author.default_avatar_url
        em.set_author(name="Gardening profile of {}".format(author.name), icon_url=avatar)
        em.add_field(name="**Thneeds**", value=str(gardener.points))
        if not gardener.current:
            em.add_field(name="**Currently growing**", value="None")
        else:
            em.set_thumbnail(url=gardener.current["image"])
            em.add_field(
                name="**Currently growing**",
                value="{0} ({1:.2f}%)".format(
                    gardener.current["name"], gardener.current["health"]
                ),
            )
        if not gardener.badges:
            em.add_field(name="**Badges**", value="None")
        else:
            badges = ""
            for badge in gardener.badges:
                badges += "{}\n".format(badge.capitalize())
            em.add_field(name="**Badges**", value=badges)
        if not gardener.products:
            em.add_field(name="**Products**", value="None")
        else:
            products = ""
            for product_name, product_data in gardener.products.items():
                if self.products[product_name] is None:
                    continue
                products += "{} ({}) {}\n".format(
                    product_name.capitalize(),
                    product_data / self.products[product_name]["uses"],
                    self.products[product_name]["modifier"],
                )
            em.add_field(name="**Products**", value=products)
        if gardener.current:
            degradation = await self._degradation(gardener)
            die_in = await _die_in(gardener, degradation)
            to_grow = await _grow_time(gardener)
            em.set_footer(
                text="Total degradation: {0:.2f}% / {1} min (100 / ({2} / 60) * (BaseDegr {3:.2f} + PlantDegr {4:.2f}))"
                " + ModDegr {5:.2f}) Your plant will die in {6} minutes "
                "and {7:.1f} minutes to go for flowering.".format(
                    degradation.degradation,
                    self.defaults["timers"]["degradation"],
                    degradation.time,
                    self.defaults["degradation"]["base_degradation"],
                    gardener.current["degradation"],
                    degradation.modifiers,
                    die_in,
                    to_grow,
                )
            )
        await ctx.send(embed=em)

    @_gardening.command(name="plants")
    async def _plants(self, ctx):
        """Look at the list of the available plants."""
        if self.plants is None:
            await self._load_plants_products()
        tick = ""
        tock = ""
        tick_tock = 0
        for plant in self.plants["all_plants"]:
            if tick_tock == 0:
                tick += "**{}**\n".format(plant["name"])
                tick_tock = 1
            else:
                tock += "**{}**\n".format(plant["name"])
                tick_tock = 0
        em = discord.Embed(title="All plants that are growable", color=discord.Color.green())
        em.add_field(name="\a", value=tick)
        em.add_field(name="\a", value=tock)
        await ctx.send(embed=em)

    @_gardening.command(name="plant")
    async def _plant(self, ctx: commands.Context, *, plantname):
        """Look at the details of a plant."""
        if not plantname:
            await ctx.send_help()
        if self.plants is None:
            await self._load_plants_products()
        t = False
        plant = None
        for p in self.plants["all_plants"]:
            if p["name"].lower() == plantname.lower().strip('"'):
                plant = p
                t = True
                break

        if t:
            em = discord.Embed(
                title="Plant statistics of {}".format(plant["name"]), color=discord.Color.green(),
            )
            em.set_thumbnail(url=plant["image"])
            em.add_field(name="**Name**", value=plant["name"])
            em.add_field(name="**Rarity**", value=plant["rarity"].capitalize())
            em.add_field(name="**Grow Time**", value="{0:.1f} minutes".format(plant["time"] / 60))
            em.add_field(name="**Damage Threshold**", value="{}%".format(plant["threshold"]))
            em.add_field(name="**Badge**", value=plant["badge"])
            em.add_field(name="**Reward**", value="{} τ".format(plant["reward"]))
        else:
            message = "I can't seem to find that plant."
            em = discord.Embed(description=message, color=discord.Color.red())
        await ctx.send(embed=em)

    @_gardening.command(name="state")
    async def _state(self, ctx):
        """Check the state of your plant."""
        author = ctx.author
        gardener = await self._gardener(author)
        try:
            await self._apply_degradation(gardener)
        except discord.Forbidden:
            # Couldn't DM the degradation
            await ctx.send("ERROR\nYou blocked me, didn't you?")

        if not gardener.current:
            message = "You're currently not growing a plant."
            em_color = discord.Color.red()
        else:
            plant = gardener.current
            degradation = await self._degradation(gardener)
            die_in = await _die_in(gardener, degradation)
            to_grow = await _grow_time(gardener)
            message = (
                "You're growing {0} **{1}**. "
                "Its health is **{2:.2f}%** and still has to grow for **{3:.1f}** minutes. "
                "It is losing **{4:.2f}%** per minute and will die in **{5:.1f}** minutes.".format(
                    plant["article"],
                    plant["name"],
                    plant["health"],
                    to_grow,
                    degradation.degradation,
                    die_in,
                )
            )
            em_color = discord.Color.green()
        em = discord.Embed(description=message, color=em_color)
        await ctx.send(embed=em)

    @_gardening.command(name="buy")
    async def _buy(self, ctx, product=None, amount: int = 1):
        """Buy gardening supplies."""
        if self.products is None:
            await self._load_plants_products()

        author = ctx.author
        if product is None:
            em = discord.Embed(
                title="All gardening supplies that you can buy:", color=discord.Color.green(),
            )
            for pd in self.products:
                em.add_field(
                    name="**{}**".format(pd.capitalize()),
                    value="Cost: {} τ\n+{} health\n-{}% damage\nUses: {}\nCategory: {}".format(
                        self.products[pd]["cost"],
                        self.products[pd]["health"],
                        self.products[pd]["damage"],
                        self.products[pd]["uses"],
                        self.products[pd]["category"],
                    ),
                )
            await ctx.send(embed=em)
        else:
            if amount <= 0:
                message = "Invalid amount! Must be greater than 1"
            else:
                gardener = await self._gardener(author)
                if product.lower() in self.products and amount > 0:
                    cost = self.products[product.lower()]["cost"] * amount
                    withdraw_points = await _withdraw_points(gardener, cost)
                    if withdraw_points:
                        if product.lower() not in gardener.products:
                            gardener.products[product.lower()] = 0
                        # gardener.products[product.lower()] += amount
                        # Only add it once
                        gardener.products[product.lower()] += (
                            amount * self.products[product.lower()]["uses"]
                        )
                        await gardener.save_gardener()
                        message = "You bought {}.".format(product.lower())
                    else:
                        message = "You don't have enough Thneeds. You have {}, but need {}.".format(
                            gardener.points, self.products[product.lower()]["cost"] * amount,
                        )
                else:
                    message = "I don't have this product."
            em = discord.Embed(description=message, color=discord.Color.green())
            await ctx.send(embed=em)

    @_gardening.command(name="convert")
    async def _convert(self, ctx: commands.Context, amount: int):
        """Exchange Thneeds for credits."""
        author = ctx.author
        gardener = await self._gardener(author)

        withdraw_points = await _withdraw_points(gardener, amount)
        plural = ""
        if amount > 0:
            plural = "s"
        if withdraw_points:
            await bank.deposit_credits(author, amount)
            message = "{} Thneed{} successfully exchanged for credits.".format(amount, plural)
            await gardener.save_gardener()
        else:
            message = "You don't have enough Thneed{}. " "You have {}, but need {}.".format(
                plural, gardener.points, amount
            )

        em = discord.Embed(description=message, color=discord.Color.green())
        await ctx.send(embed=em)

    @commands.command(name="shovel")
    async def _shovel(self, ctx: commands.Context):
        """Shovel your plant out."""
        author = ctx.author
        gardener = await self._gardener(author)
        if not gardener.current:
            message = "You're currently not growing a plant."
        else:
            gardener.current = {}
            message = "You successfully shovelled your plant out."
            if gardener.points < 0:
                gardener.points = 0
            await gardener.save_gardener()

        em = discord.Embed(description=message, color=discord.Color.dark_grey())
        await ctx.send(embed=em)

    @commands.command(name="water")
    async def _water(self, ctx):
        """Water your plant."""
        author = ctx.author
        channel = ctx.channel
        gardener = await self._gardener(author)
        try:
            await self._apply_degradation(gardener)
        except discord.Forbidden:
            # Couldn't DM the degradation
            await ctx.send("ERROR\nYou blocked me, didn't you?")
        product = "water"
        product_category = "water"
        if not gardener.current:
            message = "You're currently not growing a plant."
            await _send_message(channel, message)
        else:
            await self._add_health(channel, gardener, product, product_category)

    @commands.command(name="fertilize")
    async def _fertilize(self, ctx, fertilizer):
        """Fertilize the soil."""
        gardener = await self._gardener(ctx.author)
        try:
            await self._apply_degradation(gardener)
        except discord.Forbidden:
            # Couldn't DM the degradation
            await ctx.send("ERROR\nYou blocked me, didn't you?")
        channel = ctx.channel
        product = fertilizer
        product_category = "fertilizer"
        if not gardener.current:
            message = "You're currently not growing a plant."
            await _send_message(channel, message)
        else:
            await self._add_health(channel, gardener, product, product_category)

    @commands.command(name="prune")
    async def _prune(self, ctx):
        """Prune your plant."""
        gardener = await self._gardener(ctx.author)
        try:
            await self._apply_degradation(gardener)
        except discord.Forbidden:
            # Couldn't DM the degradation
            await ctx.send("ERROR\nYou blocked me, didn't you?")
        channel = ctx.channel
        product = "pruner"
        product_category = "tool"
        if not gardener.current:
            message = "You're currently not growing a plant."
            await _send_message(channel, message)
        else:
            await self._add_health(channel, gardener, product, product_category)

    # async def check_degradation(self):
    #     while "PlantTycoon" in self.bot.cogs:
    #         users = await self.config.all_users()
    #         for user_id in users:
    #             user = self.bot.get_user(user_id)
    #             gardener = await self._gardener(user)
    #             await self._apply_degradation(gardener)
    #         await asyncio.sleep(self.defaults["timers"]["degradation"] * 60)

    async def _apply_degradation(self, gardener):
        if gardener.current:
            degradation = await self._degradation(gardener)
            now = int(time.time())
            timestamp = gardener.current["timestamp"]
            degradation_count = (now - timestamp) // (self.defaults["timers"]["degradation"] * 60)
            degradation_count -= gardener.current["degrade_count"]
            gardener.current["health"] -= degradation.degradation * degradation_count
            gardener.points += self.defaults["points"]["growing"] * degradation_count
            gardener.current["degrade_count"] += degradation_count
            await gardener.save_gardener()
            await gardener.is_complete(now)

    async def check_completion_loop(self):
        while "PlantTycoon" in self.bot.cogs:
            now = int(time.time())
            users = await self.config.all_users()
            for user_id in users:
                user = self.bot.get_user(user_id)
                if not user:
                    continue
                gardener = await self._gardener(user)
                if not gardener:
                    continue
                try:
                    await self._apply_degradation(gardener)
                    await gardener.is_complete(now)
                except discord.Forbidden:
                    # Couldn't DM the results
                    pass
            await asyncio.sleep(self.defaults["timers"]["completion"] * 60)

    async def send_notification(self):
        while "PlantTycoon" in self.bot.cogs:
            users = await self.config.all_users()
            for user_id in users:
                user = self.bot.get_user(user_id)
                if not user:
                    continue
                gardener = await self._gardener(user)
                if not gardener:
                    continue
                try:
                    await self._apply_degradation(gardener)
                except discord.Forbidden:
                    # Couldn't DM the degradation
                    pass

                if gardener.current:
                    health = gardener.current["health"]
                    if health < self.defaults["notification"]["max_health"]:
                        message = choice(self.notifications["messages"])
                        try:
                            await user.send(message)
                        except discord.Forbidden:
                            # Couldn't DM the results
                            pass
            await asyncio.sleep(self.defaults["timers"]["notification"] * 60)

    def __unload(self):
        self.completion_task.cancel()
        # self.degradation_task.cancel()
        self.notification_task.cancel()