import io
import smtplib
import ssl
import imaplib
import email
import threading
import time
import os
import datetime
import json
import socket
from concurrent.futures import ThreadPoolExecutor, as_completed
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from queue import Queue
from colorama import init, Fore
import socks
from tqdm import tqdm
import re
import requests
import signal
import sys
import logging

def parse_smtp_line(line: str):
    if not line:
        return None

    line = line.strip()
    if not line:
        return None

    parts = line.split("|")
    if len(parts) != 4:
        return None

    host, port, user, password = parts

    try:
        port = int(port)
    except ValueError:
        return None

    return host, port, user, password

def _hard_exit(sig, frame):
    os._exit(0)

# Inicjalizacja colorama
init(autoreset=True)

# ----------------------- Konfiguracja i Stałe -----------------------
SETTINGS_FILE = "settings.json"
PROXY_SOURCES_FILE = "proxy_sources.txt"
BLOCKED_DOMAINS_FILE = "blocked-domains.txt"
RESUME_FILE = "resume_smtp_progress.txt"

# Ustawienie głównego timeoutu gniazda na starcie
socket.setdefaulttimeout(10)

# Stałe domyślne
MAX_WORKERS_DEFAULT = 60
SEMAPHORE_LIMIT_DEFAULT = 40
SMTP_OP_TIMEOUT_DEFAULT = 10
RETRY_COUNT_DEFAULT = 2
BACKOFF_BASE_DEFAULT = 3
PROXY_CHECK_TIMEOUT_DEFAULT = 10
IMAP_POLL_INTERVAL_DEFAULT = 15
IMAP_CONNECTION_TIMEOUT_DEFAULT = 45
PROXY_BLOCK_DURATION_DEFAULT = 180

# Konfiguracja loggera
class TqdmHandler(logging.Handler):
    """Handler, który używa tqdm.write, aby nie psuć paska postępu."""
    def emit(self, record):
        log_entry = self.format(record)
        tqdm.write(log_entry)

logger = logging.getLogger("SmtpChecker")
logger.setLevel(logging.INFO)
if not logger.handlers:
    formatter = logging.Formatter(f"{Fore.CYAN}[%(asctime)s] {Fore.RESET}%(levelname)s: %(message)s", datefmt="%H:%M:%S")
    tqdm_handler = TqdmHandler()
    tqdm_handler.setFormatter(formatter)
    logger.addHandler(tqdm_handler)

# ====================== FUNKCJE POMOCNICZE (Globalne) ======================

def load_settings():
    if os.path.exists(SETTINGS_FILE):
        with open(SETTINGS_FILE, 'r') as f:
            try:
                return json.load(f)
            except:
                pass
    return None

def save_settings(settings):
    try:
        with open(SETTINGS_FILE, 'w') as f:
            json.dump(settings, f, indent=4)
        logger.info(f"{Fore.GREEN}[+] Konfiguracja zapisana w {SETTINGS_FILE}")
    except Exception as e:
        logger.error(f"Blad zapisu ustawien: {e}")

def get_user_input(default_settings=None):
    s = default_settings if default_settings else {}
    print(Fore.YELLOW + "\n" + "="*40 + "\n USTAWIENIA KONFIGURACJI \n" + "="*40)
    
    # NOWA OPCJA: TRYB PRACY
    print(Fore.YELLOW + "\nTryb pracy:")
    print(" 1) SMTP + IMAP (Pełne sprawdzenie, włącznie z wysyłką)")
    print(" 2) Tylko IMAP Verify (Tylko weryfikacja skrzynki odbiorczej)")
    run_mode_input = input("Wybór (1/2): ").strip()
    run_imap_only = run_mode_input == '2'
    
    def get_input_with_default(prompt, key, default, type_func=str):
        current = s.get(key, default)
        user_input = input(f"{prompt} (aktualnie: {current}): ")
        if user_input:
            return type_func(user_input)
        return current

    # Ustawienia wspólne i dla SMTP
    smtp_file = get_input_with_default("Wprowadz plik serwerow SMTP", 'smtp_file', 'smtp.txt')
    threads = get_input_with_default("Liczba watkow (rekomendowane: 80-150)", 'threads', MAX_WORKERS_DEFAULT, int)
    to_email = get_input_with_default("E-mail odbiorcy", 'to_email', 'test@example.com')
    subject = get_input_with_default("Temat e-maila", 'subject', 'Test SMTP Verification')
    content = get_input_with_default("Tresc e-maila", 'content', 'Automatyczny test dostarczalnosci.')
    
    # Ustawienia Proxy
    use_proxy_input = input(f"Uzyc proxy? (y/n) (aktualnie: {'y' if s.get('use_proxy') else 'n'}): ").lower()
    use_proxy = use_proxy_input == 'y' if use_proxy_input else s.get('use_proxy', False)

    proxy_file = ''
    proxy_type = ''
    auto_update_proxies = False
    if use_proxy:
        proxy_file = get_input_with_default("Nazwa pliku z proxy", 'proxy_filename', 'proxies.txt')
        proxy_type = get_input_with_default("Typ proxy (http/socks4/socks5)", 'proxy_type', 'socks5').lower()
        auto_update_proxies_input = input(f"Auto-aktualizacja proxy (Scraping)? (y/n) (aktualnie: {'y' if s.get('auto_update_proxies') else 'n'}): ").lower()
        auto_update_proxies = auto_update_proxies_input == 'y' if auto_update_proxies_input else s.get('auto_update_proxies', False)

    # Ustawienia IMAP
    imap_server = get_input_with_default("Serwer IMAP", 'imap_server', 'imap.example.com')
    imap_user = get_input_with_default("Uzytkownik IMAP", 'imap_user', 'test@example.com')
    imap_pass = input("Haslo IMAP (pozostaw puste, by uzyc zapisanego): ") or s.get('imap_password', '')
    duration = get_input_with_default("Minuty monitorowania IMAP", 'duration', 5, int)

    return {
        'smtp_file': smtp_file, 'threads': threads, 'to_email': to_email, 'subject': subject,
        'content': content, 'use_proxy': use_proxy, 'proxy_filename': proxy_file if use_proxy else '',
        'proxy_type': proxy_type if use_proxy else '', 'auto_update_proxies': auto_update_proxies if use_proxy else False,
        'imap_server': imap_server, 'imap_user': imap_user, 'imap_password': imap_pass, 'duration': duration,
        'semaphore_limit': SEMAPHORE_LIMIT_DEFAULT, 'smtp_op_timeout': SMTP_OP_TIMEOUT_DEFAULT,
        'run_imap_only': run_imap_only
    }

def validate_smtp_host(host, port):
    """Sprawdza, czy DNS hosta jest resolvable."""
    try:
        socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
        return True
    except:
        return False

def resolve_host_local(host):
    """Rozwiazywanie DNS lokalnie (NIE przez 
proxy)."""
    try:
        return socket.gethostbyname(host)
    except Exception:
        return None

# ====================== KLASA SMTP CHECKER (OOP) ======================

class SmtpChecker:
    def __init__(self, settings):
        self.settings = settings
        self.proxies = []
        self.proxy_lock = threading.Lock()
        self.current_proxy_index = 0
        self.blocked_proxies = {}
        self.blocked_lock = threading.Lock()
        self.blocked_domains = set()
        self.stop_scanning_flag = False
        self.active_sockets = set()
        self.socket_lock = threading.Lock()
        self.is_smtp_phase_active = True 
        self.completed_configs = set()
        self.completed_lock = threading.Lock()

        self.semaphore = threading.Semaphore(settings.get('semaphore_limit', SEMAPHORE_LIMIT_DEFAULT))
        self.smtp_timeout = settings.get('smtp_op_timeout', SMTP_OP_TIMEOUT_DEFAULT)
        self.retry_count = settings.get('retry_count', RETRY_COUNT_DEFAULT)
        self.backoff_base = settings.get('backoff_base', BACKOFF_BASE_DEFAULT)

        signal.signal(signal.SIGINT, self._signal_handler)
        
        logger.info(Fore.CYAN + f"[*] Uzywam {settings['threads']} watkow, limit jednoczesnych polaczen: {self.semaphore._value}")
        if settings['use_proxy']:
             logger.info(Fore.CYAN + f"[*] Uzywam proxy: Typ={settings['proxy_type']}")


    # --- OBSŁUGA SYGNAŁÓW (Ctrl+C) ---

    def _signal_handler(self, sig, frame):
        """Obsługa sygnału Ctrl+C. Ubija wątki i zamyka gniazda natychmiast."""
        self.stop_scanning_flag = True
        self.is_smtp_phase_active = False 
        logger.warning(f"{Fore.MAGENTA}[!!!] Ctrl+C - koncze skanowanie...")
        self._force_close_active_sockets()
        time.sleep(0.5)

    def _force_close_active_sockets(self):
        """Silowe zamkniecie wszystkich aktywnych gniazd, aby przerwac oczekiwanie na timeouty."""
        closed_count = 0
        with self.socket_lock:
            for sock in list(self.active_sockets):
                try:
                    sock.shutdown(socket.SHUT_RDWR) 
                    sock.close()
                    closed_count += 1
                except:
                    pass
            self.active_sockets.clear()
        if closed_count > 0:
            logger.warning(f"{Fore.RED}[!!!] Silowo zamknieto {closed_count} gniazd.")

    # --- ZARZĄDZANIE DANYMI ---

    def _load_blocked_domains(self, filename):
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                domains = {line.strip().lower() for line in f if line.strip()}
                logger.info(Fore.CYAN + f"[i] Wczytano {len(domains)} zablokowanych domen.")
                return domains
        except FileNotFoundError:
            logger.warning(Fore.YELLOW + f"[!] Plik {filename} nie istnieje - blokowanie wylaczone.")
            return set()

    def _load_proxy_sources(self, filename):
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                return [line.strip() for line in f if line.strip().startswith('http')]
        except FileNotFoundError:
            return []

    def _load_proxies(self, filename):
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                return [line.strip() for line in f if line.strip()]
        except FileNotFoundError:
            return []

    def _load_smtp_servers(self, filename):
        unique = set()
        servers = []
        initial_count = 0
        try:
            with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
                for line in f:
                    initial_count += 1
                    parts = line.strip().split('|')
                    if len(parts) == 4:
                        t = tuple(parts)
                        if t not in unique:
                            unique.add(t)
                            servers.append(parts)
        except FileNotFoundError:
            logger.error(f"Nie znaleziono pliku serwerow SMTP: {filename}")
            pass
        
        removed_count = initial_count - len(servers)
        if removed_count > 0:
            logger.warning(Fore.YELLOW + f"[!] Usunieto {removed_count} duplikatow serwerow SMTP.")
        else:
            logger.info(Fore.CYAN + f"[i] Brak duplikatow serwerow SMTP w pliku {filename}.")
        return servers

    # --- ZARZĄDZANIE PROXY ---

    def scrape_and_update_proxies(self, proxy_file):
        logger.info(Fore.CYAN + "[i] Aktualizacja listy proxy...")
        sources = self._load_proxy_sources(PROXY_SOURCES_FILE)
        if not sources:
            logger.error(Fore.RED + "[!] Brak zrodel proxy w proxy_sources.txt")
            return []

        existing = self._load_proxies(proxy_file)
        unique = set(existing)
        added = 0
        
        for url in tqdm(sources, desc="Scraping", leave=False):
            try:
                r = requests.get(url, timeout=PROXY_CHECK_TIMEOUT_DEFAULT)
                found = re.findall(r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+', r.text)
                for p in found:
                    if p not in unique:
                        unique.add(p)
                        added += 1
            except:
                pass

        final = list(unique)
        with open(proxy_file, 'w', encoding='utf-8') as f:
            f.write('\n'.join(final) + '\n')
        logger.info(Fore.GREEN + f"[+] Dodano {added} nowych proxy - lacznie {len(final)}")
        return final

    def test_proxy_worker_fast(self, proxy_type, proxy_str, file_lock):
        try:
            session = requests.Session()
            if proxy_type.startswith('socks'):
                proxies = {'http': f'{proxy_type}://{proxy_str}', 'https': f'{proxy_type}://{proxy_str}'}
            else:
                proxies = {'http': f'http://{proxy_str}', 'https': f'http://{proxy_str}'}
            
            if proxy_type.startswith('socks'):
                session.mount('http://', requests.adapters.HTTPAdapter())
                session.mount('https://', requests.adapters.HTTPAdapter())

            r = session.get('http://www.google.com', proxies=proxies, timeout=PROXY_CHECK_TIMEOUT_DEFAULT, allow_redirects=False)
            if r.status_code in [200, 301, 302]: 
                with file_lock:
                    with open("good_proxies.txt", "a", encoding="utf-8") as f:
                        f.write(proxy_str + "\n")
                return proxy_str
        except Exception:
            pass
        return None

    def test_proxy_ultra_fast(self, proxy_type, proxy_list):
        logger.warning(Fore.YELLOW + "[i] Testowanie proxy (ULTRA FAST)...")
        if os.path.exists("good_proxies.txt"): os.remove("good_proxies.txt")
        
        good = []
        file_lock = threading.Lock()
        max_workers = min(300, os.cpu_count() * 10 if os.cpu_count() else 100) 
        
        with ThreadPoolExecutor(max_workers=max_workers) as ex:
            futures = {ex.submit(self.test_proxy_worker_fast, proxy_type, p, file_lock): p for p in proxy_list}
            for f in tqdm(as_completed(futures), total=len(futures), desc="Test proxy", leave=False):
                r = f.result()
                if r: good.append(r)
        return good

    def get_next_proxy(self):
        now = time.time()
        with self.blocked_lock:
            for p in list(self.blocked_proxies):
                if self.blocked_proxies[p] < now:
                    del self.blocked_proxies[p]
        
        with self.proxy_lock:
            if not self.proxies: return None
            start = self.current_proxy_index
            while True:
                proxy = self.proxies[self.current_proxy_index % len(self.proxies)]
                if proxy not in self.blocked_proxies:
                    self.current_proxy_index = (self.current_proxy_index + 1) % len(self.proxies)
                    return proxy
                self.current_proxy_index = (self.current_proxy_index + 1) % len(self.proxies)
                if self.current_proxy_index == start:
                    return None
    def wait_for_proxy(self, min_available=2, timeout=30, check_interval=1):
        """
        Czeka aż wróci min_available proxy lub do timeout.
        Zwraca True jeśli proxy wróciły, False jeśli timeout.
        """
        start = time.time()
        while time.time() - start < timeout and not self.stop_scanning_flag:
            with self.blocked_lock:
                available = [
                    p for p in self.proxies
                    if p not in self.blocked_proxies
                ]
            if len(available) >= min_available:
                return True
            time.sleep(check_interval)
        return False
    # --- BEZPIECZNE POŁĄCZENIE SMTP Z PROXY ---

    def _create_raw_socket_with_proxy(self, host, port, proxy_str=None, proxy_type=None, timeout=SMTP_OP_TIMEOUT_DEFAULT):
        if proxy_str:
            ip, pport = proxy_str.split(':')
            pport = int(pport)
            proxy_type_num = {
                'http': socks.HTTP, 'https': socks.HTTP,
                'socks4': socks.SOCKS4, 'socks5': socks.SOCKS5
            }.get(proxy_type.lower(), socks.SOCKS5)
            
            sock = socks.socksocket()
            sock.set_proxy(proxy_type_num, ip, pport)
            sock.settimeout(timeout)
            target_ip = resolve_host_local(host)
            if not target_ip:
                raise Exception("DNS resolve failed")
            sock.connect((target_ip, int(port)))
            return sock
        else:
            return socket.create_connection((host, int(port)), timeout=timeout)

    def create_smtp_connection_with_proxy(self, host, port, proxy_str=None, proxy_type=None):
        if self.stop_scanning_flag:
            return None

        context = ssl.create_default_context()
        if proxy_str:
            context.check_hostname = False
            context.verify_mode = ssl.CERT_NONE
        
        port = int(port)

        # === NO PROXY PATH ===
        if not proxy_str:
            if port == 465:
                server = smtplib.SMTP_SSL(host, port, timeout=self.smtp_timeout, context=context)
                try: server.ehlo()
                except: pass
                with self.socket_lock:
                    if server.sock: self.active_sockets.add(server.sock)
                return server
            
            server = smtplib.SMTP(host, port, timeout=self.smtp_timeout)
            server.ehlo()
            if server.has_extn('starttls'):
                server.starttls(context=context)
                server.ehlo()
            with self.socket_lock:
                if server.sock: self.active_sockets.add(server.sock)
            return server

        # === PROXY PATH ===
        orig_socket = socket.socket
        ptype = (proxy_type or 'socks5').lower()
        
        if ptype.startswith('socks5'): p_const = socks.SOCKS5
        elif ptype.startswith('socks4'): p_const = socks.SOCKS4
        else: p_const = socks.HTTP

        ip, pport = proxy_str.split(':')
        pport = int(pport)

        try:
            try: socks.setdefaultproxy(p_const, ip, pport)
            except: socks.set_default_proxy(p_const, ip, pport)
            
            socket.socket = socks.socksocket

            if port == 465:
                server = smtplib.SMTP_SSL(host, port, timeout=self.smtp_timeout, context=context)
                try: server.ehlo()
                except: pass
                with self.socket_lock:
                    if server.sock: self.active_sockets.add(server.sock)
                return server

            server = smtplib.SMTP(host, port, timeout=self.smtp_timeout)
            server.ehlo()
            if server.has_extn('starttls'):
                server.starttls(context=context)
                server.ehlo()
            with self.socket_lock:
                if server.sock: self.active_sockets.add(server.sock)
            return server
        finally:
            socket.socket = orig_socket

    def send_email(self, smtp_info, to_email, subject, content, use_proxy, proxy_type, success_queue):
        if self.stop_scanning_flag: return

        host, port, user, password = smtp_info
        try: 
            port = int(port)
        except ValueError:
            if self.is_smtp_phase_active:
                logger.error(f"[-] Nieprawidlowy port dla {host} - pomijam.")
            return

        unique_id = f"{host}|{port}|{user}|{password}"
        body = content + "\n\n[SMTP-ID] " + unique_id
        
        msg = MIMEMultipart()
        msg['From'] = user
        msg['To'] = to_email
        msg['Subject'] = subject
        msg['X-SMTP-ID'] = unique_id
        msg.attach(MIMEText(body, 'plain'))

        attempts = 0
        while attempts <= self.retry_count and not self.stop_scanning_flag:
            attempts += 1
            proxy = None
            server = None
            try:
                if use_proxy and self.proxies:
                    proxy = self.get_next_proxy()

                    if not proxy:
                        if self.is_smtp_phase_active:
                            logger.warning("[!] Brak proxy – czekam az sie zwolnia...")

                        if self.wait_for_proxy(min_available=2, timeout=30, check_interval=1):
                            proxy = self.get_next_proxy()
                        else:
                            if self.is_smtp_phase_active:
                                logger.warning("[!] Proxy nie wrocily – fallback na DIRECT")
                            proxy = None                    
                log_msg = f"[{user[:35]}...] Proba {attempts}/{self.retry_count+1} {'via '+proxy if proxy else ''}"
                
                if self.is_smtp_phase_active:
                    logger.info(log_msg)

                with self.semaphore:
                    server = self.create_smtp_connection_with_proxy(host, port, proxy, proxy_type)
                    if server is None: return

                    server.login(user, password)
                    server.sendmail(user, to_email, msg.as_string())
                    
                    with self.completed_lock:
                        self.completed_configs.add(tuple(smtp_info))

                    if self.is_smtp_phase_active:
                        logger.info(f"{Fore.GREEN}[+] Sukces: {user} ({unique_id})")
                    return

            except Exception as e:
                if self.stop_scanning_flag: return
                
                error_msg = str(e).lower()
                if self.is_smtp_phase_active:
                    logger.error(f"{Fore.RED}[-] Blad dla {user[:35]}... (Proba {attempts}): {error_msg[:70]}")
                
                proxy_errors = ['timeout', 'connection', 'proxy', 'socks', 'network', 
                                'unreachable', 'refused', 'reset', 'broken pipe', 'timed out']
                
                if proxy and any(x in error_msg for x in proxy_errors):
                    with self.blocked_lock:
                        self.blocked_proxies[proxy] = time.time() + PROXY_BLOCK_DURATION_DEFAULT
                    if self.is_smtp_phase_active:
                        logger.warning(f"[!] Proxy {proxy} zablokowane - probuje z innym...")
                
                if "authentication" in error_msg or "login" in error_msg or "password" in error_msg:
                    with self.completed_lock:
                        self.completed_configs.add(tuple(smtp_info))
                    break

                if attempts <= self.retry_count:
                    if self.is_smtp_phase_active:
                        logger.warning(f"[i] Ponawiam probe za {self.backoff_base}s...")
                    time.sleep(self.backoff_base)
                else:
                    with self.completed_lock:
                        self.completed_configs.add(tuple(smtp_info))

            finally:
                if server:
                    try: 
                        with self.socket_lock:
                            if server.sock:
                                self.active_sockets.discard(server.sock)
                        server.quit()
                    except: 
                        pass

    # --- IMAP CHECK ---
    
    def imap_check(self, server, user, password, subject, duration_minutes, success_queue):
        MAX_IMAP_RETRIES = 3
        
        original_socket = socket.socket
        try: socket.socket = socket._socket.socket if hasattr(socket, '_socket') else original_socket
        except: pass
        try: socks.setdefaultproxy(None)
        except: pass
        try:
           test_conn = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
           test_conn.settimeout(10)
           test_conn.connect((server, 993))
           test_conn.close()
        except Exception as e:
           logger.error(f"[IMAP ERROR] Host IMAP niedostępny: {e}")
           return

        id_pattern = re.compile(r'\[SMTP[-_\s]?ID\]\s*([^\s]+(?:\|[^\s]+)*)', re.IGNORECASE)
        found_ids = set()
        end_time = time.time() + duration_minutes * 60
        logger.info(Fore.CYAN + "[i] IMAP monitor running..")

        while time.time() < end_time and not self.stop_scanning_flag:
            mail = None
            retry_count = 0
            connected = False

            while retry_count <= MAX_IMAP_RETRIES and not self.stop_scanning_flag:
                try:
                    # Wymuszenie czystego socketa przed każdą próbą
                    socket.socket = original_socket
                    try:
                        socks.setdefaultproxy(None)
                    except:
                        try:
                            socks.set_default_proxy(None)
                        except:
                            pass
                    
                    mail = imaplib.IMAP4_SSL(server, timeout=IMAP_CONNECTION_TIMEOUT_DEFAULT)
                    mail.login(user, password)
                    connected = True
                    break
                except Exception as e:
                    if mail:
                        try: mail.logout()
                        except: pass
                    logger.error(f"[IMAP CONNECTION ERROR] {str(e)[:70]} (Proba {retry_count + 1}/{MAX_IMAP_RETRIES + 1})")
                    time.sleep(2 * retry_count + 1)
                    retry_count += 1
            if not connected:
                logger.error("[IMAP ERROR] Brak polaczenia - czekam...")
                time.sleep(IMAP_POLL_INTERVAL_DEFAULT)
                continue

            try:
                target_folders = ['INBOX']
                spam_folders = ['JUNK', 'SPAM', 'Spam', 'Junk'] 
                
                status, folders = mail.list()
                if status == 'OK':
                    for f in folders:
                        if not f: continue
                        f_str = f.decode()
                        match = re.search(r'\"([^\"]+)\"$', f_str)
                        if match:
                            folder_name = match.group(1).replace('"', '')
                            if folder_name.upper() not in [sf.upper() for sf in spam_folders]:
                                if folder_name not in target_folders:
                                    target_folders.append(folder_name)

                if 'INBOX' not in target_folders:
                    target_folders.insert(0, 'INBOX')
                
                target_folders = list(dict.fromkeys(target_folders))
                today = datetime.date.today().strftime("%d-%b-%Y")
                scanned = 0
                
                for folder in target_folders:
                    try:
                        mail.select(f'"{folder}"', readonly=True)
                        result, data = mail.search(None, f'SINCE "{today}" NOT DELETED')
                        if result == 'OK' and data and data[0]:
                            ids = data[0].split()
                            scanned += len(ids)
                            for msg_id in ids:
                                if self.stop_scanning_flag: break
                                try:
                                    res2, msg_data = mail.fetch(msg_id, '(BODY.PEEK[HEADER.FIELDS (SUBJECT X-SMTP-ID)] UID)')
                                    if res2 != 'OK' or not msg_data or not msg_data[0]: continue
                                    
                                    raw_header = msg_data[0][1] 
                                    msg_header = email.message_from_bytes(raw_header)
                                    
                                    if subject.lower() not in msg_header.get('Subject', '').lower(): continue
                                    
                                    smtp_id = msg_header.get('X-SMTP-ID')
                                    if not smtp_id:
                                        res2_full, msg_data_full = mail.fetch(msg_id, '(RFC822)')
                                        if res2_full == 'OK' and msg_data_full and msg_data_full[0]:
                                            raw_full = msg_data_full[0][1]
                                            msg_full = email.message_from_bytes(raw_full) 
                                            body_text = ''
                                            for part in msg_full.walk():
                                                if part.get_content_type() == 'text/plain' and part.get_payload():
                                                    try:
                                                        payload = part.get_payload(decode=True)
                                                        if payload:
                                                            body_text += payload.decode(part.get_content_charset() or 'utf-8', errors='ignore')
                                                    except: pass
                                            
                                            m = id_pattern.search(body_text)
                                            if m: smtp_id = m.group(1).strip()
                                            
                                    if smtp_id and smtp_id not in found_ids:
                                        found_ids.add(smtp_id)
                                        success_queue.put(smtp_id)
                                except: pass
                    except: pass
                
                if scanned > 0:
                    logger.info(Fore.CYAN + f"[i] Aktualnie znaleziono {len(found_ids)} potwierdzonych SMTP.")
                else:
                    logger.info(Fore.CYAN + "[i] Brak nowych wiadomosci - czekam...")

            except Exception: pass
            finally:
                if mail:
                    try: mail.logout()
                    except: pass

            time.sleep(IMAP_POLL_INTERVAL_DEFAULT)

        for sid in found_ids: success_queue.put(sid) 
        logger.info(Fore.GREEN + f"[+] IMAP monitoring finished. Found {len(found_ids)} confirmed deliveries.")
        socket.socket = original_socket


    # --- GŁÓWNA METODA URUCHAMIAJĄCA ---

    def run(self):
        if self.settings.get('run_imap_only', False):
            self.run_imap_only()
            os._exit(0)
        
        self.is_smtp_phase_active = True
        self.blocked_domains = self._load_blocked_domains(BLOCKED_DOMAINS_FILE)
        
        if self.settings['use_proxy']:
            proxy_list_raw = self.scrape_and_update_proxies(self.settings['proxy_filename']) if self.settings.get('auto_update_proxies') else self._load_proxies(self.settings['proxy_filename'])
            
            print(f"\nWczytano {len(proxy_list_raw)} proxy.")
            print("1) Sprawdz proxy (wolniej, ale pewnosc)")
            print("2) Pomijam test (szybciej)")
            mode = None
            try:
                while mode is None:
                    c = input("Wybor (1/2): ").strip()
                    if c == "1":
                        mode = "check"
                    elif c == "2":
                        mode = "skip"
                    else:
                        print("Wybierz 1 lub 2!")
            except (EOFError, KeyboardInterrupt):
                print("\nBrak wejscia - uzywam trybu skip (bez testu proxy).")
                mode = "skip"
            self.proxies = self.test_proxy_ultra_fast(self.settings['proxy_type'], proxy_list_raw) if mode == "check" else proxy_list_raw
            
            with open("good_proxies.txt", "w", encoding="utf-8") as f:
                f.write("\n".join(self.proxies) + "\n")

            if not self.proxies:
                logger.error(Fore.RED + "Brak dzialajacych proxy. Koncze.")
                return
            logger.info(Fore.GREEN + f"[+] Aktywnych proxy: {len(self.proxies)}")

        # Ładowanie serwerów
        smtp_servers_raw = []
        resume_choice = 'n'

        if os.path.exists(RESUME_FILE):
            try:
                resume_choice = input("Wznowic z resume_smtp_progress.txt? (t/n): ").lower()
            except (EOFError, KeyboardInterrupt):
                resume_choice = 'n'
                print("\nBrak wejścia - pomijam wznowienie.")

        if resume_choice == 't':
            smtp_servers_raw = self._load_smtp_servers(RESUME_FILE)
        else:
            smtp_servers_raw = self._load_smtp_servers(self.settings['smtp_file'])

        temp_list = [s for s in smtp_servers_raw if s[2].split('@')[-1].lower() not in self.blocked_domains]
        if len(temp_list) < len(smtp_servers_raw):
            logger.warning(f"[!] Usunieto {len(smtp_servers_raw) - len(temp_list)} serwerow ze wzgledu na zablokowane domeny.")
        smtp_servers_raw = temp_list

        # Walidacja DNS
        valid_smtp_servers = []
        with ThreadPoolExecutor(max_workers=self.settings['threads']) as ex:
            futures = {ex.submit(validate_smtp_host, h, int(p)): [h,p,u,w] for h,p,u,w in smtp_servers_raw}
            for f in tqdm(as_completed(futures), total=len(futures), desc="Walidacja DNS"):
                if f.result():
                    valid_smtp_servers.append(futures[f])
        
        if not valid_smtp_servers:
            logger.error(Fore.RED + "Brak poprawnych hostow SMTP do sprawdzenia.")
            return
        
        smtp_servers_to_process = {tuple(info): info for info in valid_smtp_servers}
        logger.info(Fore.CYAN + f"[i] Rozpoczynam sprawdzanie {len(smtp_servers_to_process)} serwerow SMTP...")

        # 2. Wysyłka E-maili
        success_queue = Queue()
        executor = ThreadPoolExecutor(max_workers=self.settings['threads'])
        futures_dict = {
            executor.submit(self.send_email, info, self.settings['to_email'], self.settings['subject'], self.settings['content'], 
                            self.settings['use_proxy'], self.settings['proxy_type'], success_queue): info 
            for info in smtp_servers_to_process.values()
        }

        progress_bar = tqdm(total=len(futures_dict), desc="Checking SMTP", miniters=1)
        completed_count = 0

        while completed_count < len(futures_dict) and not self.stop_scanning_flag:
            newly_completed = sum(1 for f in futures_dict if f.done()) - completed_count
            if newly_completed > 0:
                progress_bar.update(newly_completed)
                completed_count += newly_completed
            time.sleep(0.1)
        
        progress_bar.close()
        self.is_smtp_phase_active = False 
        
        if self.stop_scanning_flag:
            logger.warning(Fore.MAGENTA + "[!!!] Wymuszam zakonczenie puli watkow i zapis postepu...")
            executor.shutdown(wait=False)
            remaining = [info for info in valid_smtp_servers if tuple(info) not in self.completed_configs]
            with open(RESUME_FILE, 'w', encoding='utf-8') as f:
                f.write('\n'.join('|'.join(i) for i in remaining) + '\n')
            logger.info(Fore.MAGENTA + f"[!!!] Zapisano {len(remaining)} serwerow do wznowienia w {RESUME_FILE}.")
        else:
            executor.shutdown(wait=True)
            if os.path.exists(RESUME_FILE):
                try: os.remove(RESUME_FILE)
                except: pass

        # 4. Weryfikacja IMAP
        logger.info(Fore.CYAN + "\n[i] Wysylka zakonczona. Rozpoczynam IMAP verification.")
        self.stop_scanning_flag = False
        self.imap_check(self.settings['imap_server'], self.settings['imap_user'], 
                        self.settings['imap_password'], self.settings['subject'], 
                        self.settings['duration'], success_queue) 

        # 5. Zapis końcowy
        good = set()
        while not success_queue.empty():
            good.add(success_queue.get())
        final_good = list(good)

        with open("good.txt", "w", encoding="utf-8") as f:
            f.write('\n'.join(final_good) + '\n')
        
        logger.info(Fore.GREEN + f"\n[+] Zapisano {len(final_good)} dzialajacych SMTP do good.txt")
        print(Fore.CYAN + "="*60)
        print(Fore.CYAN + "Ai by Revo - koniec programu")
        print(Fore.CYAN + "="*60)
        os._exit(0)

    def run_imap_only(self):
        logger.info(Fore.CYAN + "\n" + "="*60)
        logger.info(Fore.CYAN + "Tryb: TYLKO WERYFIKACJA IMAP (Bez Wysylki)")
        logger.info(Fore.CYAN + "="*60 + "\n")
        self.is_smtp_phase_active = False
        success_queue = Queue()
        self.stop_scanning_flag = False
        self.imap_check(self.settings['imap_server'], self.settings['imap_user'], 
                        self.settings['imap_password'], self.settings['subject'], 
                        self.settings['duration'], success_queue) 

        good_ids = set()
        while not success_queue.empty():
            good_ids.add(success_queue.get())
        final_good = list(good_ids)
        with open("good.txt", "w", encoding="utf-8") as f:
            f.write('\n'.join(final_good) + '\n')
        print(Fore.CYAN + "Ai by Revo - koniec programu")


# ====================== PUNKT WEJŚCIA ======================

def main():
    logger.info(Fore.CYAN + "Ai by Revo - SMTP Checker + Proxy + IMAP Verification [FINAL]")

    current_settings = load_settings()
    settings = {}
    
    if current_settings:
        logger.info(Fore.CYAN + f"[i] Znaleziono plik {SETTINGS_FILE}.")
        try:
            choice = input("Chcesz uzyc zapisanych danych (s) czy wprowadzic nowe (n)? (s/n): ").lower()
        except (EOFError, KeyboardInterrupt):
            choice = 's'
            print("\nBrak wejscia - uzywam zapisanych ustawien.")
        
        if choice == 's':
            settings = current_settings
            logger.info(Fore.GREEN + "[+] Uzywam zapisanej konfiguracji.")
        else:
            settings = get_user_input(current_settings)
            save_settings(settings)
    else:
        settings = get_user_input()
        save_settings(settings)

    checker = SmtpChecker(settings)
    try:
        checker.run()
    except KeyboardInterrupt:
        pass

if __name__ == "__main__":
    main()
