import asyncio
import os
import aiohttp

from html import escape
from aiocache import cached
# از cachebox استفاده نکن، از کش ساده خودمون استفاده کن
class TTLCache:
    def __init__(self, maxsize=1000, ttl=120):
        self.maxsize = maxsize
        self.ttl = ttl
        self.cache = {}
        import time
        self._time = time
    
    def __getitem__(self, key):
        if key in self.cache:
            value, timestamp = self.cache[key]
            if self._time.time() - timestamp < self.ttl:
                return value
            else:
                del self.cache[key]
        raise KeyError(key)
    
    def __setitem__(self, key, value):
        if len(self.cache) >= self.maxsize:
            oldest = min(self.cache.keys(), key=lambda k: self.cache[k][1])
            del self.cache[oldest]
        self.cache[key] = (value, self._time.time())
    
    def __contains__(self, key):
        try:
            self.__getitem__(key)
            return True
        except KeyError:
            return False
from pyrogram import Client, filters, idle
from pyrogram.handlers import MessageHandler, CallbackQueryHandler, RawUpdateHandler, DeletedMessagesHandler
from pyrogram.enums import ParseMode
from pyrogram.types import LinkPreviewOptions
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from persiantools.jdatetime import JalaliDateTime

from app.database import Database
from app.handlers import Handlers
from app.config import ADMINS, BOT_TOKEN
from app.utils import Utils, timezone
from app.utils.path import path


class App(Handlers, Utils):
    def __init__(self) -> None:
        super().__init__()

        self.database = Database()
        self.client: Client | None = None
        self.scheduler: AsyncIOScheduler | None = None

        self.tron_price = 0
        self.ton_price = 0
        self.rec_cache = TTLCache(0, 120)

        self._update_lock = asyncio.Lock()
        self._delete_handler = None

    async def update_price(self):
        timeout = aiohttp.ClientTimeout(total=5)
        try:
            async with aiohttp.ClientSession(timeout=timeout) as session:
                async with session.get(
                    "https://api.nobitex.ir/market/stats"
                ) as response:
                    data = await response.json()

            self.tron_price = int(data['stats']['trx-rls']['mark']) / 10
        except:
            pass

        try:
            async with aiohttp.ClientSession(timeout=timeout) as session:
                async with session.get(
                    "https://api.coingecko.com/api/v3/simple/price?ids=the-open-network&vs_currencies=usd"
                ) as response:
                    data = await response.json()

            ton_usd = data['the-open-network']['usd']
            
            async with aiohttp.ClientSession(timeout=timeout) as session:
                async with session.get(
                    "https://api.nobitex.ir/market/stats"
                ) as response:
                    data = await response.json()
                    
            usd_rls = int(data['stats']['usdt-rls']['mark']) / 10
            self.ton_price = ton_usd * usd_rls
        except:
            pass

    async def group_update(self):
        async for banner in self.database.get_banners(status=0):
            if banner.banner_limit == 0:
                banner_limit = "نامحدود"
            else:
                banner_limit = f"{banner.banner_limit:,}"
        
            get_seens = await self.database.get_all_seens_count(banner_tag=banner.tag)
            
            if banner.banner_end == 1:
                banner_count = f"➿ ظرفیت: <b>{banner.count:,}</b>"
                banner_left = f"<b>{banner.count - get_seens:,}</b>"
            else:
                banner_count = f"🕐 زمان: <b>{banner.count:,} روز</b>"
                banner_left = f"<b>{(banner.count - (self.timestamp - banner.date) // 86400):,} روز</b>"
                
            if banner.banner_limit == 0 or await self.database.get_seens_count(banner_tag=banner.tag) < banner.banner_limit:
                status = "✅"
            else:
                status = "❌"
            
            text = ""
            rank = 0
            
            async for user_id, total_count in self.database.get_banner_ranks(banner.tag):
                rank += 1
                
                try:
                    get_users = await self.client.get_users(user_id)
                    full_name = get_users.full_name
                except:
                    full_name = user_id

                text += f"""{rank}: نام کاربر: <b>{full_name}</b>
〰️ تعداد سین ها: <b>{total_count:,}</b>
💵 درآمد: <b>{round((total_count / 1000) * banner.s1000):,} تومان</b>\n\n"""
             
            try:
                sent_message = await self.client.send_message(
                    chat_id=banner.stats_group,
                    text=f"""💡 مشخصات بنر به شرح زیر می باشد:

🕯 نام: <b>{escape(banner.banner_name)}</b>

🌀 محدودیت بنر: <b>{banner_limit}</b>
🔖 نوع کانال: <b>{' - '.join(list(map(lambda x: self.channel_type(x), self.decode(banner.channel_type))))}</b>

{banner_count}
✔️ سین کل: <b>{get_seens:,}</b>
〰️ باقی مانده: {banner_left}

💵 نرخ 1000 سین: <b>{banner.s1000:,} تومان</b>
💰 درآمد کل: <b>{round((get_seens / 1000) * banner.s1000):,} تومان</b>

⚜️ قابل دریافت: {status}
📆 تاریخ ساخت: <b>{self.jalali_date(banner.date)}</b>

〽️ توضیحات: <b>{banner.description}</b>"""
                )

                await sent_message.reply(
                    text=f"💡 آمار بنر <b>{escape(banner.banner_name)}</b> به شرح زیر می باشد:\n\n{text}",
                    disable_web_page_preview=LinkPreviewOptions(is_disabled=True),
                    quote=True
                )
            
            except:
                pass

    async def job_update_banners(self):
        if self._update_lock.locked():
            return

        async with self._update_lock:
            await self._do_update_banners()
            await self.done_banners()

    async def _update_banner(self, seen):
        try:
            msg = await self.client.get_messages(
                chat_id=seen.chat_id,
                message_ids=seen.msg_id
            )

            if msg.views and msg.views - 1 != seen.count:
                await self.database.update_seen(
                    seen, count=msg.views - 1
                )
        except Exception as e:
            print(e)

    async def _do_update_banners(self, banner_tag: str = None):
        get_seens = (
            self.database.get_seens(banner_tag=banner_tag, status=0)
            if banner_tag
            else self.database.get_seens(status=0)
        )

        batch = []

        async for seen in get_seens:
            batch.append(seen)

            if len(batch) == 10:
                await asyncio.gather(
                    *(self._update_banner(s) for s in batch)
                )
                batch.clear()

        if batch:
            await asyncio.gather(
                *(self._update_banner(s) for s in batch)
            )

    async def job_reshots(self):
        timestamp = self.timestamp
        current_time = JalaliDateTime.now(timezone).strftime("%H:%M")
        asyncio.create_task(self._do_reshots(timestamp, current_time))

    async def _do_reshots(self, timestamp, current_time):
        if self._update_lock.locked():
            async with self._update_lock:
                pass

        try:
            self.client.remove_handler(*self._delete_handler)
        except ValueError:
            pass

        async for schedule in self.database.get_schedules():
            if (
                (schedule.stype == 1 and int(schedule.date) > timestamp)
                or (
                    schedule.stype in [3, 4]
                    and schedule.date != current_time
                )
            ):
                continue

            banner = await self.database.get_banner(
                tag=schedule.banner_tag, status=0
            )

            if not banner:
                await self.database.delete_schedule(schedule.tag)
                continue

            if (
                schedule.stype == 2
                and timestamp - banner.last_send
                < int(schedule.date) * 60
            ):
                continue

            async for seen in self.database.get_seens(
                banner_tag=schedule.banner_tag, disable=0, status=0
            ):
                if seen.disable:
                    continue
                
                await asyncio.sleep(0.05)

                await self._update_banner(seen)

                try:
                    await self.client.delete_messages(
                        seen.chat_id, seen.msg_id
                    )
                except:
                    pass

                try:
                    sent = await self.client.forward_messages(
                        chat_id=seen.chat_id,
                        from_chat_id=self.setting.banner_channel,
                        message_ids=banner.msg_id
                    )

                    all_count = seen.count + seen.all_count
                    await self.database.update_seen(
                        seen,
                        count=0,
                        all_count=all_count,
                        msg_id=sent.id
                    )
                except:
                    pass

            if schedule.stype in [1, 3]:
                await self.database.delete_schedule(schedule.tag)

        self._delete_handler = self.client.add_handler(
            DeletedMessagesHandler(self.delete_handler, filters.channel)
        )

    async def done_banners(self):
        async for banner in self.database.get_banners(status=0):
            views = await self.database.get_all_seens_count(banner_tag=banner.tag)

            if not views:
                continue

            if (banner.banner_end == 1 and views >= banner.count) or (banner.banner_end == 2 and self.timestamp >= banner.count * 86400):
                await self.database.delete_schedules(banner.tag)
                await self.done_banner(banner, views)
                await self.database.update_banner(banner, status=1)

    async def done_banner(self, banner, get_views):
        gifts = banner.gifts.split("-")
        gift_1 = int(gifts[0])
        gift_2 = int(gifts[1])
        gift_3 = int(gifts[2])
                    
        async for seen in self.database.get_seens(banner_tag=banner.tag):
            await self.database.update_seen(seen, status=1)

            try:
                await self.client.delete_messages(
                    chat_id=seen.chat_id,
                    message_ids=seen.msg_id
                )
            except:
                pass

            amount = round(((seen.all_count + seen.count) / 1000) * banner.s1000)
            get_user = await self.database.get_user(user_id=seen.user_id)
            
            try:
                await self.client.send_message(
                    chat_id=seen.user_id,
                    text=f"""💡 حسابرسی بنر <b>{escape(banner.banner_name)}</b> انجام شد و به مقدار <b>{amount:,}</b> تومان به موجود حساب شما افزوده شد."""
                )
            except:
                pass
            
            if str(await self.database.get_rank(seen.banner_tag, seen.user_id)) == "1" and gift_1:
                amount += gift_1
                    
                try:
                    await self.client.send_message(
                        chat_id=seen.user_id,
                        text=f"""🎁 مقدار <b>{gift_1:,}</b> هدیه برای تبلیغ <b>{escape(banner.banner_name)}</b> به شما بخاطر رتبه ی اول بودن تعلق گرفت."""
                    )
                except:
                    pass
                
            elif str(await self.database.get_rank(seen.banner_tag, seen.user_id)) == "2" and gift_2:
                amount += gift_2
                    
                try:
                    await self.client.send_message(
                        chat_id=seen.user_id,
                        text=f"""🎁 مقدار <b>{gift_2:,}</b> هدیه برای تبلیغ <b>{escape(banner.banner_name)}</b> به شما بخاطر رتبه ی دوم بودن تعلق گرفت."""
                    )
                except:
                    pass
                
            elif str(await self.database.get_rank(seen.banner_tag, seen.user_id)) == "3" and gift_3:
                amount += gift_3
                    
                try:
                    await self.client.send_message(
                        chat_id=seen.user_id,
                        text=f"""🎁 مقدار <b>{gift_3:,}</b> هدیه برای تبلیغ <b>{escape(banner.banner_name)}</b> به شما بخاطر رتبه ی سوم بودن تعلق گرفت."""
                    )
                except:
                    pass
        
            await self.database.update_user(get_user, current_balance=get_user.current_balance + amount, all_balance=get_user.all_balance + amount)
                
        all_income = round((get_views / 1000) * banner.s1000)
        
        for admin in ADMINS:
            try:
                await self.client.send_message(
                    chat_id=admin,
                    text=f"💡 تبلیغ <b>{escape(banner.banner_name)}</b> حسابرسی شد.\n\n✔️ بازدید کل: <b>{get_views:,}</b>\n💰 درآمد کل: <b>{all_income:,} تومان</b>"
                )
            except:
                pass

    def initialize_handlers(self):
        self.client.add_handler(MessageHandler(self.start_handler, filters.private & filters.command('start') & ~filters.me))
        self.client.add_handler(MessageHandler(self.message_handler, filters.private & filters.text & ~filters.me))
        self.client.add_handler(MessageHandler(self.media_handler, filters.private & filters.photo & ~filters.me))
        self.client.add_handler(CallbackQueryHandler(self.callback_query_handler))
        self._delete_handler = self.client.add_handler(DeletedMessagesHandler(self.delete_handler, filters.channel))
        self.client.add_handler(RawUpdateHandler(self.raw_update_handler))

    async def restart_bot(self):
        await self.client.stop()
        self.initialize_handlers()
        await self.client.start()
        
    async def _run(self):
        await self.database.initialize()

        if not await self.database.get_setting():
            await self.database.add_setting(start="start")

        self.setting = await self.database.get_setting()

        self.client = Client(
            path('tg/session'),
            5,
            '1c5c96d5edd401b1ed40db3fb5633e2d',
            bot_token=BOT_TOKEN,
            parse_mode=ParseMode.HTML
        )

        self.initialize_handlers()

        self.scheduler = AsyncIOScheduler(timezone="Asia/Tehran")

        self.scheduler.add_job(
            self.update_price,
            'cron',
            second=0
        )

        self.scheduler.add_job(
            self.job_update_banners,
            'cron',
            minute='*/5'
        )

        self.scheduler.add_job(
            self.group_update,
            'cron',
            minute=0
        )

        self.scheduler.add_job(
            self.job_reshots,
            'cron',
            second=0
        )

        await self.client.start()
        self.scheduler.start()

        print("Gostarde View Is Running!")

        try:
            await idle()
        finally:
            self.scheduler.shutdown(wait=False)
            await self.client.stop()
            os._exit(0)

    def run(self):
        asyncio.run(self._run())