import logging
import disnake
from disnake.ext import commands
from disnake.ext import tasks
import requests
import numpy as np
import asyncio
import sys
import os
import copy
import datetime
import math
import random
import json
import shutil
from constants.global_constants import *


def setup(bot):
	bot.add_cog(MainEconomyModule(bot))

class MainEconomyModule(commands.Cog):
	def __init__(self, client):
		self.client = client
		self.DataBaseManager = client.DataBaseManager
		
	@commands.Cog.listener()
	async def on_ready(self):
		self.krekchat = await self.client.fetch_guild(constants["krekchat"])
		self.sponsors = [disnake.utils.get(self.krekchat.roles, id=i) for i in constants["sponsors"]]
		self.me = disnake.utils.get(self.krekchat.roles, id=constants["me"])
		logging.info(f'KrekFunBot economy module activated')

	@commands.slash_command(name = "статистика", description="Статистика отображает все данные о пользователе")
	async def UserStatistic(self, ctx: disnake.AppCmdInter,
					  member: disnake.Member = commands.Param(description="Чью статистику хотите посмотреть?", name="участник", default=None)):
		if not member:
			member = ctx.author

		if ctx.guild is None:
			embed = self.client.InfoEmbed(title=f"Статистика **{member.name}**", description=f'')
		elif member.nick != None:
			embed = self.client.InfoEmbed(title=f"Статистика **{member.nick}**", description=f'')
		else:
			embed = self.client.InfoEmbed(title=f"Статистика **{member.name}**", description=f'')

		embed.set_thumbnail(url=member.avatar)

		await ctx.response.defer()

		users_model = self.DataBaseManager.models['users'].m
		rimagochi_users_model = self.DataBaseManager.models['rimagochi_users'].m

		async with self.DataBaseManager.session() as session:
			async with session.begin():
				user = await session.get(users_model, member.id)
				if user is None:
					embed = self.client.ErrEmbed(title="Ошибка статистики", description="Такого пользователя нет в базе данных")
					await ctx.edit_original_message(embed=embed)
					return

				stmt = self.DataBaseManager.select(users_model).options(
					self.DataBaseManager.selectinload(users_model.custom_roles),
					self.DataBaseManager.joinedload(users_model.creation_role),
					self.DataBaseManager.selectinload(users_model.prize_roles),
					self.DataBaseManager.selectinload(users_model.sender_in_crumbs_transactions),
					self.DataBaseManager.selectinload(users_model.recipient_in_crumbs_transactions),
					self.DataBaseManager.joinedload(users_model.casino_account),
				).where(
					users_model.id == member.id
				)
				user = (await session.execute(stmt)).scalars().first()

				stmt = self.DataBaseManager.select(rimagochi_users_model).options(
					self.DataBaseManager.selectinload(rimagochi_users_model.animals),
					self.DataBaseManager.selectinload(rimagochi_users_model.battle_slots_animals)
				).where(
					rimagochi_users_model.id == member.id
				)
				rimagochi_user = (await session.execute(stmt)).scalars().first()

				embed.add_field(name=f"Модуль экономики", value=f"", inline=False)
				embed.add_field(name=f"крошки", value=f"{user.crumbs}", inline=False)
				embed.add_field(name=f"сообщения (в общем)", value=f"{user.summary_messages}", inline=False)
				embed.add_field(name=f"минут в голосовом канале (в общем)", value=f"{user.summary_voice_activity/60}", inline=False)
				embed.add_field(name=f"сообщения (после сброса)", value=f"{user.period_messages}", inline=False)
				embed.add_field(name=f"минут в голосовом канале (после сброса)", value=f"{user.period_voice_activity/60}", inline=False)
				embed.add_field(name=f"репутация", value=f"{user.carma}", inline=False)
				embed.add_field(name=f"зарплата", value=f"{user.staff_salary}", inline=False)
				embed.add_field(name=f"время получения последней ежедневной награды", value=f"<t:{int(user.last_daily_crumbs_date)}:F>", inline=False)
				embed.add_field(name=f"время последней активности", value=f"<t:{int(user.last_activity_date)}:F>", inline=False)
				embed.add_field(name=f"количество записей в истории об отправлениях (общая сумма)", value=f"{len(user.sender_in_crumbs_transactions)} ({sum([write.amount for write in user.sender_in_crumbs_transactions]+[0])})", inline=False)
				embed.add_field(name=f"количество записей в истории о получениях (общая сумма)", value=f"{len(user.recipient_in_crumbs_transactions)} ({sum([write.amount for write in user.recipient_in_crumbs_transactions]+[0])})", inline=False)
				embed.add_field(name=f"наличие записей в казино", value=f"{not user.casino_account is None}", inline=False)
				if not user.casino_account is None:
					embed.add_field(name=f"количество круток сегодня (может быть не актуальным)", value=f"{user.casino_account.spins_today_count}", inline=False)
					embed.add_field(name=f"время последнего сброса лимитов", value=f"<t:{int(user.casino_account.last_reset_time)}:F>", inline=False)

				embed.add_field(name=f"Ролевой модуль", value=f"", inline=False)
				embed.add_field(name=f"количество кастомных ролей (общая стоимость)", value=f"{len(user.custom_roles)} ({sum([role.cost for role in user.custom_roles]+[0])})", inline=False)
				embed.add_field(name=f"количество призовых ролей", value=f"{len(user.prize_roles)}", inline=False)
				embed.add_field(name=f"созданная кастомная роль", value=(f"<@&{user.creation_role.id}>" if not user.creation_role is None else "None"), inline=False)

				embed.add_field(name=f"Модуль rimagochi", value=f"", inline=False)
				embed.add_field(name=f"наличие аккаунта rimagochi", value=f"{not rimagochi_user is None}", inline=False)
				if not rimagochi_user is None:
					embed.add_field(name=f"количество зверей", value=f"{len(rimagochi_user.animals)}", inline=False)
					embed.add_field(name=f"количество генов", value=f"{sum([int(i) for i in rimagochi_user.genes.values()])}", inline=False)
					embed.add_field(name=f"количество предметов", value=f"{sum([int(i) for i in rimagochi_user.items.values()])}", inline=False)
					embed.add_field(name=f"количество побед", value=f"{rimagochi_user.wins}", inline=False)
				await ctx.edit_original_message(embed=embed)

	@commands.slash_command(description="Показывает топ участников сервера по заданному параметру (по умолчанию - уровень)", name="топ")
	async def TopSlash(self, ctx: disnake.AppCmdInter, parameter: str = commands.Param(description="По какому критерию хотите увидеть топ?",
														name="критерий", default = 'по уровню',
														choices=['по уровню', 'по количеству сообщений', 'по времени в голосовых каналах', 'по количеству крошек'])):
		await ctx.response.defer()
		async with self.DataBaseManager.session() as session:
			async with session.begin():
				async with self.DataBaseManager.models['users'] as users_model:
					order_by = None
					match parameter:
						case "по уровню":
							order_by = [self.DataBaseManager.desc(users_model.period_messages + (users_model.period_voice_activity / 180))]
						case "по количеству сообщений":
							order_by = [self.DataBaseManager.desc(users_model.period_messages), self.DataBaseManager.desc(users_model.period_voice_activity)]
						case "по времени в голосовых каналах":
							order_by = [self.DataBaseManager.desc(users_model.period_voice_activity), self.DataBaseManager.desc(users_model.period_messages)]
						case "по количеству крошек":
							order_by = [self.DataBaseManager.desc(users_model.crumbs), self.DataBaseManager.desc(users_model.period_messages), self.DataBaseManager.desc(users_model.period_voice_activity)]
					if order_by is None:
						return
					stmt = self.DataBaseManager.select(users_model).order_by(*order_by).limit(20)
					users = (await session.execute(stmt)).scalars().all()

		embed = self.client.InfoEmbed(title=f"Общий топ {parameter}", description=f'')
		users_counter = 0
		for user in users:
			try:
				member = await self.krekchat.fetch_member(user.id)
			except disnake.NotFound:
				continue
			member_name = None
			if member.nick != None:
				member_name = member.nick
			elif member.display_name != None:
				member_name = member.display_name
			else:
				member_name = member.name
			match parameter:
				case "по уровню":
					embed.add_field(name=f"{users_counter + 1}) {member_name}",
									value=f"{member.mention} - {int(self.client.CalculateLevel(user.period_messages, user.period_voice_activity))} уровень",
									inline=False)
				case "по количеству сообщений":
					embed.add_field(name=f"{users_counter + 1}) {member_name}",
									value=f"{member.mention} - {user.period_messages} сообщений",
									inline=False)
				case "по времени в голосовых каналах":
					embed.add_field(name=f"{users_counter + 1}) {member_name}",
									value=f"{member.mention} - {int(user.period_voice_activity/60)} минут",
									inline=False)
				case "по количеству крошек":
					embed.add_field(name=f"{users_counter + 1}) {member_name}",
									value=f"{member.mention} - {int(user.crumbs)} крошек",
									inline=False)
			users_counter += 1

			if users_counter >= 10:
				break

		await ctx.edit_original_message(embed=embed)


	@commands.slash_command(name="ежедневная")
	async def DailySlash(self, ctx):
		pass
	@DailySlash.sub_command(
		description=f"Получайте награду в крошках каждый день",
		name="награда")
	async def DailyCrumbsSub(self, ctx: disnake.AppCmdInter):
		await ctx.response.defer()
		err_embed = self.client.ErrEmbed(title="Ошибка начисления ежедневной награды")
		if ctx.guild is None:
			err_embed.description = "Эта команда не работает в личных сообщениях!"
			await ctx.edit_original_message(embed=err_embed)
			return
		async with self.DataBaseManager.session() as session:
			await self.client.UserUpdate(member = ctx.author, session = session)
			async with session.begin():
				async with self.DataBaseManager.models['users'] as users_model:
					stmt = self.DataBaseManager.select(users_model).where(users_model.id == ctx.author.id).with_for_update()
					user = (await session.execute(stmt)).scalars().first()

					result = await user.claim_daily_crumbs(daily_constant = constants['dailycrumbs'], member = ctx.author, sponsor_roles = self.sponsors)

					if result['success']:
						history = self.DataBaseManager.models['transaction_history_crumbs'].m(recipient_id = ctx.author.id, amount = result['count'], description = f"Ежедневная награда за {result['date'].strftime('%d.%m.%Y')}")
						session.add(history)
						await ctx.edit_original_message(embed=self.client.InfoEmbed(description=result['output']))
						return
					else:
						err_embed.description = result['output']
						await ctx.edit_original_message(embed=err_embed)
						return


	@commands.slash_command(name="история", description="Показывает историю транзакций")
	async def HistoryCrumbsSlash(self, ctx: disnake.AppCmdInter):
		pass
	@HistoryCrumbsSlash.sub_command(description="Показывает историю транзакций", name="транзакций")
	async def HistoryCrumbsSub(self, ctx: disnake.AppCmdInter, member_inp: disnake.Member = commands.Param(
							  description="Чью историю хотите посмотреть?(только для администраторов)",
							  name="участник", default=None)):
		
		member = ctx.author
		if isinstance(ctx.author, disnake.Member) and (member_inp is not None) and (self.me in ctx.author.roles):
			member = member_inp
		class HistoryButtons(disnake.ui.View):
			def __init__(self, ctx, transactions, member, embed):
				super().__init__(timeout=180)
				self.transactions = transactions
				self.ctx = ctx
				self.embed = embed
				self.member = member
				self.page = 1
				self.maxpage = len(transactions) if len(transactions) > 0 else 1

				self.left.disabled = (self.page == 1)
				self.right.disabled = (self.page == self.maxpage)

			@disnake.ui.button(label="<", custom_id="left", style=disnake.ButtonStyle.secondary)
			async def left(self, button, inter):
				if inter.author != self.ctx.author:
					return
				self.page -= 1
				self.left.disabled = (self.page == 1)
				self.right.disabled = (self.page == self.maxpage)
				self.embed = await EmbedHistoryChanger(self.transactions, self.embed, self.page, self.member)
				await inter.response.edit_message(embed=self.embed, view=self)

			@disnake.ui.button(label=">", custom_id="right", style=disnake.ButtonStyle.secondary)
			async def right(self, button, inter):
				if inter.author != self.ctx.author:
					return
				self.page += 1
				self.left.disabled = (self.page == 1)
				self.right.disabled = (self.page == self.maxpage)
				self.embed = await EmbedHistoryChanger(self.transactions, self.embed, self.page, self.member)
				await inter.response.edit_message(embed=self.embed, view=self)

			async def on_timeout(self):
				for child in self.children:
					if isinstance(child, (disnake.ui.Button, disnake.ui.BaseSelect)):
						child.disabled = True
				await self.ctx.edit_original_message(view=self)
		async def EmbedHistoryChanger(transactions, embed, selfpage, member):
			embed.clear_fields()
			if len(transactions) == 0:
				embed.add_field(name=f"В истории пока ничего нет", value=f"", inline=False)
				return embed
			embed.add_field(name=f"", value=f"", inline=False)
			page = transactions[selfpage - 1]
			maxpage = len(transactions) if len(transactions) > 0 else 1
			c = 1
			for transaction in transactions[selfpage - 1]:
				if transaction.recipient_id == member.id:
					string = (
						f"{c+5*(selfpage-1)}) <:A_g_wplus:606920277443608593> **{int(transaction.amount)} крошек** {f' (комиссия: {int(transaction.commission_fraction * 100)}%)'if transaction.commission_fraction != 0 else ''}\n"
						f"{f'Отправитель: <@{transaction.sender_id}>' if not transaction.sender_id is None else ''}\n"
						f"Время транзакции: <t:{int(transaction.transaction_time)}:f>\n"
						f"> {transaction.description}"
					)
				else:
					string = (
						f"{c+5*(selfpage-1)}) <:A_g_wmins:606920287044239362> **{int(transaction.amount)} крошек** {f' (комиссия: {int(transaction.commission_fraction * 100)}%)'if transaction.commission_fraction != 0 else ''}\n"
						f"{f'Получатель: <@{transaction.recipient_id}>' if not transaction.recipient_id is None else ''}\n"
						f"Время транзакции: <t:{int(transaction.transaction_time)}:f>\n"
						f"> {transaction.description}"
					)
				embed.add_field(name=f"", value=string, inline=False)

				c += 1
			embed.add_field(name=f"", value=f"Страница {selfpage}/{maxpage}", inline=False)
			return embed
		await ctx.response.defer(ephemeral = True)
		embed = self.client.InfoEmbed(title=f'История транзакций {member.name}', description=f"")

		async with self.DataBaseManager.session() as session:
			async with session.begin():
				async with self.DataBaseManager.models['transaction_history_crumbs'] as transaction_history_crumbs_model:
					stmt = self.DataBaseManager.select(transaction_history_crumbs_model).where(
						self.DataBaseManager.or_(
							transaction_history_crumbs_model.sender_id == member.id,
							transaction_history_crumbs_model.recipient_id == member.id
						)
					).order_by(self.DataBaseManager.desc(transaction_history_crumbs_model.id))
					transactions = (await session.execute(stmt)).scalars().all()

		readyarray = self.client.PartitioningEmbeder(transactions)
		view = HistoryButtons(ctx, readyarray, member, embed)

		embed = await EmbedHistoryChanger(readyarray, embed, 1, member)
		await ctx.edit_original_message(embed=embed, view=view)


	@commands.slash_command(name="казино",
						  description="Хотите иметь очень много крошек? Тогда, эта команда точно не для вас)")
	async def CasinoSlash(self, ctx: disnake.AppCmdInter,
						  count: commands.Range[int, 1, ...] = commands.Param(description="Сколько крошек хотите поставить на кон?", name="ставка"),
						  possibility: commands.Range[int, 1, 99] = commands.Param(
							  description="Шанс на победу. Чем выше, тем меньше крошек вы получите после победы(целое число от 1 до 99)",
							  name="шанс", default=50),
						  quantity: int = commands.Param(
							  description=f"Количество круток(целое число от 1 до {constants['casinospinslimit']}({constants['casinospinslimit']*2} для спонсоров))",
							  name="количество", default=1)):
		await ctx.response.defer()

		err_embed = self.client.ErrEmbed(title="Ошибка казино")
		if isinstance(ctx.author, disnake.User):
			err_embed.description = "Эта команда не работает в личных сообщениях!"
			await ctx.edit_original_message(embed=err_embed)
			return
		def get_dynamic_fee(count: int) -> float:
			if count < 1_000:
				return 0
			elif count < 5_000:
				return 0.1
			elif count < 10_000:
				return 0.2
			elif count < 25_000:
				return 0.3
			elif count < 50_000:
				return 0.4
			elif count < 75_000:
				return 0.5
			elif count < 100_000:
				return 0.6
			elif count < 125_000:
				return 0.7
			elif count < 150_000:
				return 0.8
			else:
				return 0.9
		def calculate_multiplier(possibility: float, count: int) -> float:
			fee = get_dynamic_fee(count)
			multiplier = ((0.909/possibility)-0.918)*(1-fee)
			return multiplier*count

		async with self.DataBaseManager.session() as session:
			await self.client.CasinoUserUpdate(member = ctx.author, session = session)
			async with session.begin():
				users_model = self.DataBaseManager.models['users'].m
				casino_account_model = self.DataBaseManager.models['casino_user_account'].m
				stmt = self.DataBaseManager.select(users_model).where(users_model.id == ctx.author.id).with_for_update()
				user = (await session.execute(stmt)).scalars().first()
				stmt = self.DataBaseManager.select(casino_account_model).where(casino_account_model.id == ctx.author.id).with_for_update()
				casino_user = (await session.execute(stmt)).scalars().first()
				user_is_sponsor = await user.in_role(member = ctx.author, roles = self.sponsors)
				casinospinslimit = constants['casinospinslimit']
				if quantity <= 0:
					err_embed.description = f'Количество круток должно быть > 0'
					await ctx.edit_original_message(embed=err_embed)
					return
				if quantity > casinospinslimit * (user_is_sponsor + 1) and (not self.me in ctx.author.roles):
					err_embed.description = f'Вы не можете крутить казино больше {casinospinslimit * (user_is_sponsor + 1)} раз в день!'
					await ctx.edit_original_message(embed=err_embed)
					return
				if not 0 < possibility < 100:
					err_embed.description = f'Шанс должен быть от 1% до 99%'
					await ctx.edit_original_message(embed=err_embed)
					return
				possibility = possibility // 100
				if count <= 0:
					err_embed.description = f'Ставка должна быть больше 0'
					await ctx.edit_original_message(embed=err_embed)
					return
				if user.crumbs < count * quantity:
					err_embed.description = f'Для такой ставки вам необходимо иметь {quantity*count} крошек'
					await ctx.edit_original_message(embed=err_embed)
					return

				now = datetime.datetime.now().timestamp()

				if now - casino_user.last_reset_time >= 86400:
					casino_user.spins_today_count = 0
					casino_user.last_reset_time = now

				if casino_user.spins_today_count + quantity > casinospinslimit * (user_is_sponsor + 1) and (not self.me in ctx.author.roles):
					if casino_user.spins_today_count >= casinospinslimit * (user_is_sponsor + 1):
						err_embed.description = f'У вас не осталось круток, возобновление лимита будет <t:{casino_user.last_reset_time + 86400}:R>'
						await ctx.edit_original_message(embed=err_embed)
					else:
						err_embed.description = f'У вас осталось только {casinospinslimit * (user_is_sponsor + 1) - (casino_user.spins_today_count)} круток, возобновление лимита будет <t:{casino_user.last_reset_time + 86400}:R>'
						await ctx.edit_original_message(embed=err_embed)
					return

				results = [random.random() < possibility for _ in range(quantity)]
				totalwins = sum(results)
				totalcrumbs = totalwins * calculate_multiplier(possibility, count) - (quantity - totalwins) * count

				casino_user.spins_today_count += quantity

				user.crumbs += totalcrumbs
				if int(totalcrumbs)>0:
					history = self.DataBaseManager.models['transaction_history_crumbs'].m(recipient_id = ctx.author.id, amount = int(totalcrumbs), description = f"Выигрыш в казино с шансом {int(possibility * 100)}% и ставкой {count} крошек ({totalwins} побед, {quantity-totalwins} поражений)")
					session.add(history)
					embed=self.client.InfoEmbed(title=f'Вы в плюсе!', description=f"Ваш выигрыш составил {int(totalcrumbs)}!")
				elif int(totalcrumbs) == 0:
					embed=self.client.InfoEmbed(title=f'Вы вышли в 0',description=f"")
				else:
					history = self.DataBaseManager.models['transaction_history_crumbs'].m(sender_id = ctx.author.id, amount = int(abs(totalcrumbs)), description = f"Проигрыш в казино с шансом {int(possibility * 100)}% и ставкой {count} крошек ({totalwins} побед, {quantity-totalwins} поражений)")
					session.add(history)
					embed=self.client.InfoEmbed(title=f'Вы в минусе(', description=f"Ваш проигрыш составил {int(-totalcrumbs)}")
				
				if embed.description is None:
					raise ValueError("embed.description в казино = None")
				embed.description += (f"\n-# У вас осталось {casinospinslimit * (user_is_sponsor + 1) - (casino_user.spins_today_count)} круток")
				await ctx.edit_original_message(embed = embed)


	@commands.slash_command(name="отдать")
	async def GiveCrumbsSlash(self, ctx):
		pass
	@GiveCrumbsSlash.sub_command(description=f"Передайте свои крошки другому участнику сервера (комиссия {int(constants['givecrumbscommission']*100)}%)", name="крошки")
	async def GiveCrumbsSub(self, ctx: disnake.AppCmdInter,
							member: disnake.Member = commands.Param(description="Кому вы хотите перевести крошки?",
																	name="адресат"),
							count: int = commands.Param(description="Сколько крошек вы хотите перевести?",
														name="количество"),
							comment: str = commands.Param(description="Комментарий к переводу", name="комментарий",
														  default="Перевод"),
							commission: float = commands.Param(description="Доля комиссии(доступно только админам)",
															  name="комиссия", default=None)):
		
		err_embed = self.client.ErrEmbed(err_func = ctx.response.send_message, title="Ошибка перевода")
		if isinstance(ctx.author, disnake.User):
			await err_embed.send("Эта команда не работает в личных сообщениях!")
			return
		if comment != "Перевод":
			comment = "Перевод | " + comment
		if (commission == None) or (not self.me in ctx.author.roles) or (ctx.author.id == 515542927158804480):
			commission = constants['givecrumbscommission']
		if count <= 0:
			await err_embed.send("Количество крошек должно быть больше 0")
			return
		users_model = self.DataBaseManager.models['users'].m
		async with self.DataBaseManager.session() as session:
			async with session.begin():
				author_data = await session.get(users_model, ctx.author.id, with_for_update = True)
				member_data = await session.get(users_model, member.id, with_for_update = True)

				if author_data is None:
					await err_embed.send(f'Вас нет в базе данных')
					return

				if member_data is None:
					await err_embed.send(f'Такого пользователя нет в базе данных')
					return

				if author_data.crumbs - count < 0:
					await err_embed.send(f'У вас не хватает крошек')
					return

				history_write = self.DataBaseManager.models["transaction_history_crumbs"].m(sender_id = ctx.author.id, recipient_id = member.id, commission_fraction = commission, description = comment, amount = count)
				session.add(history_write)
				author_data.crumbs -= count
				member_data.crumbs += count * (1 - commission)

				await ctx.response.send_message(embed=self.client.InfoEmbed(
					description=f'{count} крошек успешно отправлено на счёт {member.mention}, комиссия составила {int(commission * 100)}%({int(count * commission)} крошек)'))