import logging
logging.getLogger('telethon').setLevel(logging.CRITICAL)

from dataclasses import dataclass, field
from typing import Union, Optional

from urllib import request
from telethon.tl.functions.help import (GetCountriesListRequest, GetAppConfigRequest)
from telethon.tl.types import (
    JsonObject, JsonObjectValue, JsonString, JsonNumber,
    EmailVerifyPurposeLoginSetup, EmailVerificationCode, CodeSettings
)
from telethon.tl.functions.account import (
    SendVerifyEmailCodeRequest, VerifyEmailRequest, RegisterDeviceRequest
)
from telethon import TelegramClient, functions, types, errors
from telethon.tl.functions.langpack import GetLangPackRequest
from telethon.tl.functions.auth import SendCodeRequest

from peewee import fn
from dotenv import load_dotenv
from datetime import datetime
from typing import Union
import requests
import asyncio
import aiohttp
import base64
import shutil
import random
import string
import json
import os
import re
import traceback
SOCKS_PROXY_URL = (
    "socks5://"
    "f4f1dff52b67a4c46d05__nocr.cu,cn,af"
    ":3332fdb136e46ea9"
    "@gw.dataimpulse.com:824"
)

from utils.functions import (
    get_random_device_model, get_random_system_version,
    get_random_app_version, get_country_name,get_phone_language,
    get_timezone, convert_timezone, _packageid, _installer, get_random_bio
)
from utils.db import PushToken, Email, Setting

load_dotenv()

@dataclass
class Gmail:
    address: str
    request_id: str
    issucess: bool
    used: int = field(default=0)
    code: str = field(default=None)

class EmailKeeper:
    _queue: asyncio.Queue = asyncio.Queue()

    @classmethod
    async def add_gmail(cls, gmail: "Gmail") -> None:
        if gmail.used < 2:
            await cls._queue.put(gmail)
    
    @classmethod
    async def get_gmail_async(cls, timeout: float = 0.0) -> Optional["Gmail"]:
        try:
            if timeout > 0:
                gmail = await asyncio.wait_for(cls._queue.get(), timeout=timeout)
            else:
                gmail = cls._queue.get_nowait()
        except (asyncio.QueueEmpty, asyncio.TimeoutError):
            return None

        return gmail

class Register:
    def __init__(self, phone_number: str, user_id: Union[str, int] = None, proxy: dict = None) -> None:
        self.phone_number = phone_number
        self.push_token = None
        self.hash = None
        self.user_id = user_id
        
        self.tg_api = {
            'beta': {'api_id': 4, 'api_hash': '014b35b6184100b085b0d0572f9b5103', 'hint': 'Public Android Beta'},
            'google': {'api_id': 6, 'api_hash': 'eb06d4abfb49dc3eeb1aeb98ae0f581e', 'hint': 'Distributed Android'},
            'standalone': {'api_id': 6, 'api_hash': 'eb06d4abfb49dc3eeb1aeb98ae0f581e', 'hint': 'Distributed Android'}
        }
        
        self.api_key = Setting.get().antisafety_api_key
        self.api_key_email = '540916392:cVkqKkXSQCOBsbDYTgVT'
        self.reghelp_api_key = 'IOqq1lcNxONsbgQcqqHILvjf1edWDNeG'
        
        self.api_hash = self.tg_api[Setting.get().app_mode].get('api_hash')
        self.api_id = self.tg_api[Setting.get().app_mode].get('api_id')
        
        self.device_model = get_random_device_model()
        self.system_version = get_random_system_version()
        self.app_version = Setting.get().app_version
        self.lang_pack = 'android'
        
        self.is_proxy = False
        self.proxy = proxy
        
        if self.user_id:
            os.makedirs(f'registeration/otp_sessions/{self.user_id}', exist_ok=True)
            os.makedirs(f'registeration/temp_sessions/{self.user_id}', exist_ok=True)
            os.makedirs(f'registeration/valid_sessions/{self.user_id}', exist_ok=True)
        else:
            os.makedirs(f'registeration/otp_sessions/', exist_ok=True)
            os.makedirs(f'registeration/temp_sessions/', exist_ok=True)
            os.makedirs(f'registeration/valid_sessions/', exist_ok=True)
        
        if self.is_proxy:
            import socket, socks
            socks.set_default_proxy(socks.SOCKS5, self.proxy['addr'], self.proxy['port'], True, self.proxy['username'], self.proxy['password'])
            socket.socket = socks.socksocket

        self.client = TelegramClient(
            session = f'registeration/temp_sessions/{self.user_id}/{self.phone_number}',
            api_id = self.api_id,
            api_hash = self.api_hash,
            proxy = self.proxy
        )

    async def _patch_client(self):
        self.push_token = await self.get_push_token(local=Setting.get().use_local_pushtoken)
        self.language = await get_phone_language(get_country_name(self.phone_number))
        self.time_zone = convert_timezone(await get_timezone(get_country_name(self.phone_number)))
        
        self.client._init_request.device_model = self.device_model
        self.client._init_request.system_version = self.system_version
        self.client._init_request.app_version = self.app_version
        self.client._init_request.system_lang_code = '-'.join(self.language)
        self.client._init_request.lang_code = self.language[0]
        self.client._init_request.lang_pack = self.lang_pack
        
        self.client._init_request.params = JsonObject([
            JsonObjectValue('device_token', JsonString(self.push_token)),
            JsonObjectValue('data', JsonString('49C1522548EBACD46CE322B6FD47F6092BB745D0F88082145CAF35E14DCC38E1')),
            JsonObjectValue('installer', JsonString(_installer(Setting.get().app_mode))),
            JsonObjectValue('package_id', JsonString(_packageid(Setting.get().app_mode))),
            JsonObjectValue('tz_offset', JsonNumber(self.time_zone)),
            JsonObjectValue('perf_cat', JsonNumber(2)),
        ])
    
    async def get_push_token(self, local: bool = False) -> str:
        if local:
            pt = PushToken.select().order_by(fn.Random()).limit(1).first()
            return pt.token if pt else ''
        
        async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False), trust_env=True) as session:
            url = f'https://antisafety.net/api/tasks/android-push/create?api_key={self.api_key}'

            async with session.get(url) as response:
                key_id = (await response.json())['id']
                get_url = f'https://antisafety.net/api/tasks/android-push/get?api_key={self.api_key}&id={key_id}'

                while True:
                    async with session.get(get_url) as response:
                        response = await response.json()

                    if response.get('token'):
                        PushToken.create(api_key=self.api_key, token=response['token'])
                        return response['token']

                    await asyncio.sleep(1)

    async def get_safety_net_token(self) -> str:
        nonce = base64.b64encode(self.phone_number.encode())

        async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False), trust_env=True) as session:
            async with session.get(f'https://antisafety.net/api/tasks/safetynet/create?api_key={self.api_key}&nonce={nonce.decode()}') as response:
                key_id = (await response.json())['id']

                while True:
                    async with session.get(f'https://antisafety.net/api/tasks/safetynet/get?api_key={self.api_key}&id={key_id}') as response:
                        response = await response.json()

                    if response.get('status') and response['status'] == 'ok':
                        return response['token']

                    await asyncio.sleep(1)

    async def get_safety_net_token(self) -> str:
        nonce = base64.b64encode(self.phone_number.encode())

        async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False), trust_env=True) as session:
            async with session.get(f'https://antisafety.net/api/tasks/safetynet/create?api_key={self.api_key}&nonce={nonce.decode()}') as response:
                key_id = (await response.json())['id']

                while True:
                    async with session.get(f'https://antisafety.net/api/tasks/safetynet/get?api_key={self.api_key}&id={key_id}') as response:
                        response = await response.json()

                    if response.get('status') and response['status'] == 'ok':
                        return response['token']

                    await asyncio.sleep(1)

    async def get_email(self):
        connector = ProxyConnector.from_url(SOCKS_PROXY_URL)

        async with aiohttp.ClientSession(
            connector=connector,
            trust_env=False
        ) as session:
            url = f"https://venusads.ir/api/V1/email/getEmail/?key={self.api_key_email}&server=1"

            async with session.get(url, ssl=False) as response:
                data = await response.json()

                # ✅ چک کامل
                if not isinstance(data, dict):
                    raise Exception(f"Invalid response: {data}")

                if data.get("status") != 200:
                    raise Exception(f"API error: {data}")

                email = data.get("email")
                request_id = data.get("requestID")

                if not email or not request_id:
                    raise Exception(f"Empty email or requestID: {data}")

                Email.create(
                    api_key=self.api_key,
                    email=email,
                    uuid=request_id
                )

                return request_id, email

    async def get_email_code(self, key_id: str) -> Union[str, bool]:
        connector = ProxyConnector.from_url(SOCKS_PROXY_URL)

        try:
            async with aiohttp.ClientSession(
                connector=connector,
                trust_env=False
            ) as session:
                for _ in range(5):
                    url = f'https://venusads.ir/api/V1/email/getCode/?key={self.api_key_email}&id={key_id}'

                    async with session.get(url, ssl=False) as response:
                        response_json = await response.json()
                        print(f'[get_email_code] Response -> {response_json}')

                    code = response_json.get('code')
                    if code:
                        return code

                    await asyncio.sleep(6)

            return False

        except Exception as error:
            print(error)
            return False

    async def get_recaptcha_token(self, app_key: str) -> str:
        async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(ssl=False), trust_env=True) as session:
            url = f'https://api.reghelp.net/RecaptchaMobile/getToken?apiKey={self.reghelp_api_key}&appName={_packageid(Setting.get().app_mode)}&appKey={app_key}&appAction=signup&proxyType={self.proxy.get("proxy_type")}&proxyAddress={self.proxy.get("addr")}&proxyPort={self.proxy.get("port")}&proxyLogin={self.proxy.get("username")}&proxyPassword={self.proxy.get("password")}&appDevice=android'

            async with session.get(url) as response:
                key_id = (await response.json())['id']
                get_url = f'https://api.reghelp.net/RecaptchaMobile/getStatus?apiKey={self.reghelp_api_key}&id={key_id}'

                while True:
                    async with session.get(get_url) as response:
                        response = await response.json()

                    if response.get('token'):
                        PushToken.create(api_key=self.api_key, token=response['token'])
                        return response['token']

                    await asyncio.sleep(1)
    
    async def _sms_functions(self) -> bool:
        nearest_dc = await self.client(functions.help.GetNearestDcRequest())
        lang_pack = await self.client(GetLangPackRequest('android', 'en'))
        countries = await self.client(GetCountriesListRequest('en', 0))
        return True

    async def get_sms_code(self):
        try:
            await self.client.connect()
            
            self.client._app_config_hash = (await self.client(GetAppConfigRequest(0))).hash
            dc = await self._sms_functions()
            
            response = await self.client.send_code_request(self.phone_number)
            self.hash = response.phone_code_hash
            
            return response
        except errors.rpcerrorlist.PhoneNumberInvalidError:
            return False, 'PhoneNumberInvalidError'
        except errors.rpcerrorlist.FloodWaitError:
            return False, 'FloodWaitError'
        except errors.rpcerrorlist.PhoneNumberBannedError:
            return False, 'PhoneNumberBannedError'
        except ConnectionError as e:
            return False, 'ConnectionError'
        except Exception as e:
            # if 'RECAPTCHA_CHECK_signup' in str(e):
            #     match = re.search(r"RECAPTCHA_CHECK_signup__([A-Za-z0-9_-]+)", str(e))
            #     if match:
            #         app_key = match.group(1)
            #         print(f'appKey: {app_key}')
            #         token = await self.get_recaptcha_token(app_key=app_key)
            #         print(token)
            #         response = await self.client(functions.InvokeWithReCaptchaRequest(
            #             token = token,
            #             query = functions.auth.SendCodeRequest(
            #                 phone_number=self.phone_number,
            #                 api_id=self.api_id,
            #                 api_hash=self.api_hash,
            #                 settings=types.CodeSettings(
            #                     allow_flashcall=True,
            #                     current_number=True,
            #                     allow_app_hash=True,
            #                     allow_missed_call=True,
            #                     allow_firebase=True
            #                 )
            #             )
            #         ))
            #         self.hash = response.phone_code_hash
            #         print(response)
            
            #         return response
            return False, e

    async def need_setup_email(self, type) -> bool:
        return isinstance(type, (types.auth.SentCodeTypeSetUpEmailRequired))
    
    async def need_resend_code(self, response) -> bool:
        return isinstance(response.next_type, (types.auth.CodeTypeSms))

    async def need_call_request(self, response) -> bool:
        return isinstance(response.type, (types.auth.SentCodeTypeSms)) and isinstance(response.next_type, (types.auth.CodeTypeCall))

    async def resend_code_request(self):
        response = await self.client(
            functions.auth.ResendCodeRequest(
                phone_number=self.phone_number,
                phone_code_hash=self.hash
            )
        )
        self.hash = response.phone_code_hash
        return response

    async def alreay_exists(self, response):
        return isinstance(response.type, (types.auth.SentCodeTypeApp)) and not isinstance(getattr(response, "next_type", None), (types.auth.CodeTypeSms))
    
    async def payment_required(self, response):
        return isinstance(response.sent_code, types.auth.SentCodePaymentRequired)
    
    async def setup_email(self):
        try:
            email = await EmailKeeper.get_gmail_async()

            if email == None:
                request_id, address = await self.get_email()
                email = Gmail(
                    address=address,
                    request_id=request_id,
                    issucess=True if email and request_id else False,
                    code=None,
                    used=0,
                )
            
            await self.client(
                SendVerifyEmailCodeRequest(
                    email=email.address,
                    purpose=EmailVerifyPurposeLoginSetup(phone_number=self.phone_number, phone_code_hash=self.hash),
                )
            )
            
            code = await self.get_email_code(email.request_id)
            if code:
                response = await self.client(
                    VerifyEmailRequest(
                        purpose=EmailVerifyPurposeLoginSetup(phone_number=self.phone_number, phone_code_hash=self.hash),
                        verification=EmailVerificationCode(code=code),
                    )
                )
                
                email.used += 1
                EmailKeeper.add_gmail(email)
                return response, 'success'
            else:
                return False, 'EmailCodeInvalid'
        
        except Exception as error:
            return False, error
    
    async def setup_manual_email(self, email: str):
        try:
            response = await self.client(
                SendVerifyEmailCodeRequest(
                    email=email,
                    purpose=EmailVerifyPurposeLoginSetup(phone_number=self.phone_number, phone_code_hash=self.hash)
                )
            )
        
            return response
        except Exception as error:
            return False
    
    async def setup_manual_email_code(self, code: int):
        try:
            response = await self.client(
                VerifyEmailRequest(
                    purpose=EmailVerifyPurposeLoginSetup(phone_number=self.phone_number, phone_code_hash=self.hash),
                    verification=EmailVerificationCode(code=code)
                )
            )
        
            return response
        except Exception as error:
            return False

    async def relogin(self):
        try:
            new_client = TelegramClient(
                session = f'registeration/valid_sessions/{self.user_id}/{self.phone_number}',
                api_id = 16623,
                api_hash = '8c9dbfe58437d1739540f5d53c72ae4b',
                device_model = self.device_model,
                app_version = self.app_version,
                system_version = self.system_version,
                system_lang_code = 'en-US',
                lang_code = 'en',
                proxy = self.proxy
            )
            
            await new_client.connect()
            hash = await new_client.send_code_request(self.phone_number)
            code = False
            
            if hash.phone_code_hash:
                await asyncio.sleep(2)
                if not self.client.is_connected():
                    await self.client.connect()
                
                messages = await self.client.get_messages(777000, limit=1)
                for message in messages:
                    if message.text:
                        match = re.search(r'\b\d{5}\b', message.text)
                        if match:
                            code = int(match.group())
                                
                if code:
                    print(f'Login code: {code}')

                    await new_client.sign_in(
                        phone=self.phone_number,
                        phone_code_hash=hash.phone_code_hash,
                        code=code,
                    )
                    
                    await asyncio.sleep(2)
                    await self.client.log_out()

                    get_me = await new_client.get_me()
                    with open(f'registeration/valid_sessions/{self.user_id}/{self.phone_number}.json', 'w', encoding='utf-8') as f:
                        json.dump(
                            {
                                'session_file': f'{self.phone_number}.session',
                                'phone_number': self.phone_number,
                                'app_id': 16623,
                                'app_hash': '8c9dbfe58437d1739540f5d53c72ae4b',
                                'device_model': self.device_model,
                                'sdk': self.system_version,
                                'app_version': self.app_version,
                                'system_lang_pack': 'en-US',
                                'lang_code': 'en',
                                'first_name': get_me.first_name,
                                'last_name': get_me.last_name if get_me.last_name else None,   
                                'username': get_me.username if get_me.username else None,
                                'id': get_me.id,
                                '2FA': None
                            },
                            f,
                            ensure_ascii=False,
                            indent=4
                        )
        except Exception as error:
            traceback.print_exc()
            return False, error

    async def sign_up(self, code: int, first_name: str, last_name: str = '') -> bool:
        try:
            setting = Setting.get()
            
            await self.client.sign_up(
                code            = code,
                first_name      = first_name,
                last_name       = last_name if setting.set_lastname else '',
                phone_code_hash = self.hash
            )
            
            await self.client(RegisterDeviceRequest(
                token       = self.push_token,
                app_sandbox = False,
                secret      = os.urandom(256),
                other_uids  = [],
                token_type  = 2
            ))
            
            await self.client(functions.account.UpdateStatusRequest(offline = False))
            
            # await self.client.send_message('me', 'Welcome!')
            
            if setting.set_username:
                try:
                    username = ''.join(random.choices(string.ascii_letters + string.digits, k=11))
                    await self.client(functions.account.UpdateUsernameRequest(
                        username = username
                    ))
                except:
                    username = None
            else:
                username = None
            
            if setting.set_bio:
                try:
                    about = get_random_bio()
                    await self.client(functions.account.UpdateProfileRequest(
                        about = about
                    ))
                except:
                    pass
            
            if setting.join_channel:
                try:
                    channels = setting.channels.split('-') if '-' in setting.channels else [setting.channels]
                    num_channels = len(channels)
                    selected_channels = random.sample(channels, num_channels)
                    for channel in selected_channels:
                        await self.client(functions.channels.JoinChannelRequest(channel=channel))
                except:
                    pass
            
            if setting.set_password:
                try:
                    await self.client.edit_2fa(new_password = setting.password, hint = 'Jack~')
                except:
                    pass
            
            try:
                await self.client.disconnect()
            except:
                pass
            
            await self.relogin()
            # await asyncio.to_thread(shutil.move, f'registeration/temp_sessions/{self.user_id}/{self.phone_number}.session', f'registeration/valid_sessions/{self.user_id}/{self.phone_number}.session')
            
            await asyncio.sleep(2)

            # with open(f'registeration/valid_sessions/{self.user_id}/{self.phone_number}.json', 'w', encoding='utf-8') as f:
            #      json.dump(
            #          {
            #              'session_file': f'{self.phone_number}.session',
            #              'phone_number': self.phone_number,
            #              'app_id': self.api_id,
            #              'api_hash': self.api_hash,
            #              'device_model': self.device_model,
            #              'system_version': self.system_version,
            #              'app_version': self.app_version,
            #              'first_name': first_name,
            #              'last_name': last_name,
            #              'username': username,
            #              'last_online': int(datetime.now().timestamp()),
            #              'system_lang_code': self.language[0] + '-' + self.language[1],
            #              'lang_code': self.language[0],
            #              'lang_pack': self.lang_pack,
            #              'push_token': self.push_token,
            #              'time_zone': self.time_zone,
            #              '2FA': setting.password
            #          },
            #          f,
            #          ensure_ascii=False,
            #          indent=4
            #      )
            
            return True, 'success'
        
        except Exception as error:
            return False, error
    