import asyncio
import re
import random
import json
import os
from datetime import datetime
from typing import List, Set
import logging
from concurrent.futures import ThreadPoolExecutor

from telethon import TelegramClient
from telethon.tl.types import Message
from telethon.errors import (
    FloodWaitError,
    ChannelPrivateError,
    UsernameNotOccupiedError,
)
from dotenv import load_dotenv

# تنظیمات لاگ
log_dir = os.path.join(os.path.dirname(__file__), "logs")
os.makedirs(log_dir, exist_ok=True)

data_dir = os.path.join(os.path.dirname(__file__), "data")
os.makedirs(data_dir, exist_ok=True)

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler(os.path.join(log_dir, "proxy_bot.log"), encoding="utf-8"),
        logging.StreamHandler(),
    ],
)
logger = logging.getLogger(__name__)

load_dotenv()


class ProxyCache:
    """کلاس مدیریت کش فایل‌محور"""

    def __init__(self, cache_file: str = None):
        if cache_file is None:
            cache_file = os.path.join(data_dir, "proxy_cache.json")
        self.cache_file = cache_file
        self.cache = self._load_cache()

    def _load_cache(self) -> dict:
        """بارگذاری کش از فایل"""
        try:
            if os.path.exists(self.cache_file):
                with open(self.cache_file, "r", encoding="utf-8") as f:
                    return json.load(f)
        except Exception as e:
            logger.error(f"Error loading cache: {e}")
        return {"sent_proxies": [], "collected_proxies": [], "last_update": None}

    def _save_cache(self):
        """ذخیره کش در فایل"""
        try:
            with open(self.cache_file, "w", encoding="utf-8") as f:
                json.dump(self.cache, f, ensure_ascii=False, indent=2)
        except Exception as e:
            logger.error(f"Error saving cache: {e}")

    def add_sent_proxy(self, proxy: str):
        """اضافه کردن پروکسی ارسال شده"""
        if proxy not in self.cache["sent_proxies"]:
            self.cache["sent_proxies"].append(proxy)
            # حفظ فقط آخرین 1000 پروکسی ارسال شده
            if len(self.cache["sent_proxies"]) > 1000:
                self.cache["sent_proxies"] = self.cache["sent_proxies"][-1000:]
            self._save_cache()

    def is_sent(self, proxy: str) -> bool:
        """بررسی ارسال شدن پروکسی"""
        return proxy in self.cache["sent_proxies"]

    def update_collected_proxies(self, proxies: List[str]):
        """بروزرسانی پروکسی‌های جمع‌آوری شده"""
        self.cache["collected_proxies"] = proxies
        self.cache["last_update"] = datetime.now().isoformat()
        self._save_cache()

    def get_unsent_proxies(self) -> List[str]:
        """دریافت پروکسی‌های ارسال نشده"""
        return [p for p in self.cache["collected_proxies"] if not self.is_sent(p)]


class ProxyBot:
    def __init__(
        self, api_id: int, api_hash: str, phone_number: str, backup_channel: str
    ):
        self.api_id = api_id
        self.api_hash = api_hash
        self.phone_number = phone_number
        self.backup_channel = backup_channel

        # استفاده از session فایل در دایرکتوری اصلی
        session_path = os.path.join(os.path.dirname(__file__), "proxy_reader")
        self.client = TelegramClient(session_path, api_id, api_hash)

        cache_file = os.environ.get(
            "CACHE_FILE", os.path.join(data_dir, "proxy_cache.json")
        )
        self.cache = ProxyCache(cache_file)
        self.patterns = [
            # vmess://
            re.compile(r"vmess://[A-Za-z0-9+/=]+", re.IGNORECASE),
            # vless://
            re.compile(r"vless://[a-fA-F0-9-]+@[^:\s]+:[0-9]+[^\s]*", re.IGNORECASE),
            # trojan://
            re.compile(r"trojan://[^@\s]+@[^:\s]+:[0-9]+[^\s]*", re.IGNORECASE),
            # ss://
            re.compile(r"ss://[A-Za-z0-9+/=]+@[^:\s]+:[0-9]+[^\s]*", re.IGNORECASE),
            # ssr://
            re.compile(r"ssr://[A-Za-z0-9+/=]+", re.IGNORECASE),
            # MTProto - لینک کامل
            re.compile(
                r"(?:tg://proxy\?|https://t\.me/proxy\?)server=[^&\s]+&port=[0-9]+&secret=[^\s]+",
                re.IGNORECASE,
            ),
        ]

    async def start(self):
        """شروع کلاینت"""
        try:
            await self.client.start(phone=self.phone_number)
            logger.info("✅ Bot client started successfully!")

            # تست اتصال
            me = await self.client.get_me()
            logger.info(f"🔑 Logged in as: {me.first_name} ({me.username or me.phone})")

        except Exception as e:
            logger.error(f"❌ Error starting client: {str(e)}")
            raise

    def extract_proxies(self, text: str) -> List[str]:
        """استخراج ساده‌تر پروکسی‌ها از متن"""
        if not text or not text.strip():
            return []

        proxies = []

        # استخراج پروکسی‌های معمولی
        for pattern in self.patterns:
            try:
                matches = pattern.findall(text)
                proxies.extend(matches)
            except Exception as e:
                logger.warning(f"Error with pattern: {str(e)}")

        # استخراج MTProto ساده (حتی با secret ناقص)
        # mtproto_proxies = self.extract_simple_mtproto(text)
        # proxies.extend(mtproto_proxies)

        return list(set(proxies))  # حذف تکراری

    def extract_simple_mtproto(self, text: str) -> List[str]:
        """استخراج MTProto با secret ناقص"""
        mtproto_proxies = []

        # فرمت‌های ساده MTProto
        patterns = [
            # IP:PORT:SECRET (حتی اگر secret کوتاه باشد)
            r"([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}):([0-9]+):([a-fA-F0-9]+)",
            # Server Port Secret در خطوط جداگانه
            r"(?i)server[:\s]*([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})[,\s\n]*port[:\s]*([0-9]+)[,\s\n]*secret[:\s]*([a-fA-F0-9]+)",
        ]

        for pattern in patterns:
            try:
                matches = re.findall(pattern, text, re.MULTILINE)
                for match in matches:
                    if len(match) == 3:
                        ip, port, secret = match
                        # بررسی ساده IP و Port
                        if (
                            self.is_valid_ip(ip)
                            and self.is_valid_port(port)
                            and len(secret) >= 1
                        ):
                            # ایجاد لینک MTProto حتی با secret ناقص
                            mtproto_link = f"https://t.me/proxy?server={ip}&port={port}&secret={secret}"
                            mtproto_proxies.append(mtproto_link)
            except Exception as e:
                logger.warning(f"Error extracting MTProto: {str(e)}")

        return mtproto_proxies

    def is_valid_ip(self, ip: str) -> bool:
        """بررسی ساده IP"""
        try:
            parts = ip.split(".")
            return len(parts) == 4 and all(0 <= int(part) <= 255 for part in parts)
        except:
            return False

    def is_valid_port(self, port: str) -> bool:
        """بررسی ساده Port"""
        try:
            return 1 <= int(port) <= 65535
        except:
            return False

    async def collect_from_channel(self, channel: str) -> List[str]:
        """جمع‌آوری پروکسی از یک کانال"""
        proxies = []
        try:
            # اضافه کردن تاخیر برای جلوگیری از flood
            await asyncio.sleep(2)

            # بررسی دسترسی به کانال
            try:
                entity = await self.client.get_entity(channel)
            except (ChannelPrivateError, UsernameNotOccupiedError) as e:
                logger.warning(f"⚠️ {channel}: Access denied or not found - {str(e)}")
                return []

            message_count = 0
            async for message in self.client.iter_messages(entity, limit=50):
                if message and message.text:
                    found_proxies = self.extract_proxies(message.text)
                    proxies.extend(found_proxies)
                    message_count += 1

            logger.info(
                f"✅ {channel}: {len(proxies)} proxies from {message_count} messages"
            )
            return proxies

        except FloodWaitError as e:
            logger.warning(f"⚠️ {channel}: Rate limited, waiting {e.seconds} seconds")
            await asyncio.sleep(e.seconds)
            return []
        except Exception as e:
            logger.error(f"❌ Error in {channel}: {str(e)}")
            return []

    async def collect_all_proxies_parallel(self, channels: List[str]) -> List[str]:
        """جمع‌آوری موازی از همه کانال‌ها با محدودیت همزمانی"""
        logger.info(f"🔄 Collecting from {len(channels)} channels...")

        # محدود کردن تعداد همزمان برای جلوگیری از flood
        semaphore = asyncio.Semaphore(2)  # حداکثر 2 کانال همزمان

        async def limited_collect(channel):
            async with semaphore:
                return await self.collect_from_channel(channel)

        tasks = [limited_collect(channel) for channel in channels]
        results = await asyncio.gather(*tasks, return_exceptions=True)

        all_proxies = []
        for i, result in enumerate(results):
            if isinstance(result, list):
                all_proxies.extend(result)
            else:
                logger.error(f"❌ Channel {channels[i]} failed: {result}")

        unique_proxies = list(set(all_proxies))
        logger.info(f"📊 Total: {len(all_proxies)} | Unique: {len(unique_proxies)}")

        return unique_proxies

    def create_simple_message(self, proxy: str) -> str:
        """ایجاد پیام ساده برای یک پروکسی"""
        timestamp = datetime.now().strftime("%H:%M")

        # تشخیص نوع پروکسی
        if proxy.startswith("vmess://"):
            proxy_type = "VMess"
            emoji = "🔵"
        elif proxy.startswith("vless://"):
            proxy_type = "VLESS"
            emoji = "🟢"
        elif proxy.startswith("trojan://"):
            proxy_type = "Trojan"
            emoji = "🔴"
        elif proxy.startswith("ss://"):
            proxy_type = "Shadowsocks"
            emoji = "🟡"
        elif "proxy?" in proxy:
            proxy_type = "MTProto"
            emoji = "🟦"
        else:
            proxy_type = "Proxy"
            emoji = "⚪"

        message = f"🌟 ═════════════════ 🌟\n"
        message += f"🚀 **PREMIUM PROXY** 🚀\n"
        message += f"🌟 ═════════════════ 🌟\n\n"
        message += f"📅 **{timestamp}** | {emoji} **{proxy_type}**\n\n"

        if "proxy?" in proxy:
            message += f"{proxy}\n\n"
        else:
            message += f"```{proxy}```\n\n"

        message += f"🌟 ═════════════════ 🌟\n"
        message += f"💎 **Follow @fastify_proxy!** 💎\n"
        message += f"🌟 ═════════════════ 🌟"

        return message

    async def send_single_proxy(self, proxy: str):
        """ارسال یک پروکسی"""
        try:
            message = self.create_simple_message(proxy)
            await self.client.send_message(
                self.backup_channel, message, parse_mode="markdown"
            )
            self.cache.add_sent_proxy(proxy)
            logger.info(f"✅ Sent proxy: {proxy[:50]}...")
        except FloodWaitError as e:
            logger.warning(f"⚠️ Flood wait: {e.seconds} seconds")
            await asyncio.sleep(e.seconds)
        except Exception as e:
            logger.error(f"❌ Error sending proxy: {str(e)}")

    async def load_channels(self, file_path: str) -> List[str]:
        """خواندن کانال‌ها از فایل"""
        try:
            if not os.path.exists(file_path):
                logger.error(f"❌ Channels file not found: {file_path}")
                return []

            with open(file_path, "r", encoding="utf-8") as f:
                channels = [
                    line.strip()
                    for line in f
                    if line.strip() and not line.strip().startswith("#")
                ]

            logger.info(f"📋 Loaded {len(channels)} channels from {file_path}")
            return channels
        except Exception as e:
            logger.error(f"Error loading channels: {str(e)}")
            return []

    async def collection_task(self, channels_file: str):
        """وظیفه جمع‌آوری پروکسی‌ها (هر 30 دقیقه)"""
        # اولین جمع‌آوری فوری
        logger.info("🚀 Starting initial collection...")
        channels = await self.load_channels(channels_file)

        if channels:
            proxies = await self.collect_all_proxies_parallel(channels)
            self.cache.update_collected_proxies(proxies)
            logger.info(f"✅ Initial collection: {len(proxies)} proxies")
        else:
            logger.warning("⚠️ No channels loaded for initial collection!")

        while True:
            try:
                logger.info("🔄 Starting collection cycle...")
                channels = await self.load_channels(channels_file)

                if channels:
                    proxies = await self.collect_all_proxies_parallel(channels)
                    self.cache.update_collected_proxies(proxies)
                    logger.info(f"✅ Updated cache with {len(proxies)} proxies")
                else:
                    logger.warning("⚠️ No channels loaded!")

                # انتظار 30 دقیقه
                logger.info("💤 Waiting 30 minutes for next collection...")
                await asyncio.sleep(30 * 60)

            except Exception as e:
                logger.error(f"❌ Collection error: {str(e)}")
                await asyncio.sleep(60)  # انتظار 1 دقیقه در صورت خطا

    async def sending_task(self):
        """وظیفه ارسال پروکسی‌ها (هر دقیقه)"""
        # انتظار کمی برای شروع جمع‌آوری
        await asyncio.sleep(10)

        while True:
            try:
                unsent_proxies = self.cache.get_unsent_proxies()

                if unsent_proxies:
                    # انتخاب تصادفی یک پروکسی
                    proxy = random.choice(unsent_proxies)
                    await self.send_single_proxy(proxy)
                    logger.info(f"📊 Remaining unsent: {len(unsent_proxies) - 1}")
                else:
                    logger.info("💤 No unsent proxies available")

                # انتظار 1 دقیقه
                await asyncio.sleep(60)

            except Exception as e:
                logger.error(f"❌ Sending error: {str(e)}")
                await asyncio.sleep(30)  # انتظار 30 ثانیه در صورت خطا

    async def run_forever(self, channels_file: str):
        """اجرای مداوم بات"""
        logger.info("🚀 Starting bot in continuous mode...")

        # شروع هر دو وظیفه به صورت موازی
        await asyncio.gather(self.collection_task(channels_file), self.sending_task())

    async def disconnect(self):
        """قطع اتصال"""
        try:
            await self.client.disconnect()
            logger.info("✅ Client disconnected")
        except Exception as e:
            logger.error(f"❌ Disconnect error: {str(e)}")


def get_required_env(key: str, default=None) -> str:
    value = os.environ.get(key, default)
    if not value:
        raise ValueError(f"Required environment variable {key} is not set")
    return value


async def main():
    # خواندن تنظیمات از متغیرهای محیطی یا فایل .env
    API_ID = int(get_required_env("API_ID"))  # No defaults
    API_HASH = get_required_env("API_HASH")
    PHONE_NUMBER = get_required_env("PHONE_NUMBER")
    BACKUP_CHANNEL = get_required_env("BACKUP_CHANNEL", "@fastify_proxy")
    CHANNELS_FILE = get_required_env("CHANNELS_FILE", "channels.txt")

    logger.info(f"🔧 Configuration:")
    logger.info(f"   API_ID: {API_ID}")
    logger.info(f"   PHONE: {PHONE_NUMBER}")
    logger.info(f"   BACKUP_CHANNEL: {BACKUP_CHANNEL}")
    logger.info(f"   CHANNELS_FILE: {CHANNELS_FILE}")

    bot = ProxyBot(API_ID, API_HASH, PHONE_NUMBER, BACKUP_CHANNEL)

    try:
        await bot.start()
        await bot.run_forever(CHANNELS_FILE)
    except KeyboardInterrupt:
        logger.info("🛑 Bot stopped by user")
    except Exception as e:
        logger.error(f"❌ Bot error: {str(e)}")
        raise
    finally:
        await bot.disconnect()


if __name__ == "__main__":
    asyncio.run(main())
