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 _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 = 100
SEMAPHORE_LIMIT_DEFAULT = 50
SMTP_OP_TIMEOUT_DEFAULT = 10
RETRY_COUNT_DEFAULT = 1
BACKOFF_BASE_DEFAULT = 3
PROXY_CHECK_TIMEOUT_DEFAULT = 5
IMAP_POLL_INTERVAL_DEFAULT = 15
IMAP_CONNECTION_TIMEOUT_DEFAULT = 35
PROXY_BLOCK_DURATION_DEFAULT = 200

# 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. Dla proxy wymusza IPv4."""
    try:
        # Próba resolucji IPv4 (AF_INET)
        socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
        return True
    except socket.gaierror:
        # Jeśli IPv4 nie działa, sprawdź czy ma tylko IPv6
        try:
            result = socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
            # Jeśli wszystkie wyniki to IPv6, odrzuć (PySocks nie obsługuje)
            has_ipv4 = any(addr[0] == socket.AF_INET for addr in result)
            return has_ipv4
        except:
            return False
    except:
        return False

# ====================== 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 
        # DODANO: Lista do śledzenia faktycznie zakończonych konfiguracji
        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 # ZATRZYMAJ LOGOWANIE BŁĘDÓW SMTP Z UBITYCH WĄTKÓW
        logger.warning(f"{Fore.MAGENTA}[!!!] Ctrl+C - koncze skanowanie...")
        self._force_close_active_sockets() # NATYCHMIASTOWE UBICIE ZAWIESZONYCH GNIAZD
        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:
                    # Zamknięcie w trybie SHUT_RDWR (READ/WRITE) wymusza zakończenie operacji
                    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

    # ZMIANA: Dodano file_lock i zapis w locie (append)
    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]: 
                # ZAPIS W LOCIE
                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") # Reset pliku przed testem
        
        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:
            # PRZEKAZANIE f_lock do workera
            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

    # --- 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)
            sock.connect((host, 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()
        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
                return server
            server = smtplib.SMTP(host, port, timeout=self.smtp_timeout)
            server.ehlo()
            if server.has_extn('starttls'):
                server.starttls(context=context)
                server.ehlo()
            return server

        # === PROXY PATH - Wymuszenie IPv4 dla PySocks ===
        try:
            addr_info = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
            if not addr_info:
                raise Exception("Brak adresu IPv4 dla hosta")
            ipv4_host = addr_info[0][4][0]
        except Exception as e:
            raise Exception(f"IPv6-only host (PySocks nie obsluguje): {host}")

        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(ipv4_host, port, timeout=self.smtp_timeout, context=context)
                try:
                    server.ehlo()
                except:
                    pass
                return server

            server = smtplib.SMTP(ipv4_host, port, timeout=self.smtp_timeout)
            server.ehlo()
            if server.has_extn('starttls'):
                server.starttls(context=context)
                server.ehlo()
            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.error(f"[-] {user[:35]}... -> Brak dostepnych proxy.")
                        break
                    
                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())
                    
                    # OZNACZANIE JAKO ZAKOŃCZONE (POPRAWKA RESUME)
                    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]}")
                
                # ZMIANA: Blokuj proxy tylko przy błędach sieciowych/timeoutach
                if proxy and any(x in error_msg for x in ['timeout', 'connection', 'proxy', 'socks', 'network']):
                    with self.blocked_lock:
                        self.blocked_proxies[proxy] = time.time() + PROXY_BLOCK_DURATION_DEFAULT
                
                # ZMIANA: Jeśli błąd to login/hasło, uznaj za zakończone (nie ponawiaj w nieskończoność i nie wznawiaj)
                if "authentication" in error_msg or "login" 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:
                    # Wykorzystano wszystkie próby - oznacz jako zakończone
                    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:
           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:
                    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 Exception as e: 
                                    pass
                    except Exception as e:
                        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 as e:
                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

        # 1. Ładowanie i filtrowanie
        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 = ""
            while True:
                c = input("Wybor (1/2): ").strip()
                if c == "1": mode = "check"; break
                if c == "2": mode = "skip"; break

            # ZAPIS W LOCIE (Używa test_proxy_ultra_fast z file_lock)
            self.proxies = self.test_proxy_ultra_fast(self.settings['proxy_type'], proxy_list_raw) if mode == "check" else proxy_list_raw
            
            # Finalny zapis wszystkich dobrych po teście
            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 = []
        if os.path.exists(RESUME_FILE) and input("Wznowic z resume_smtp_progress.txt? (t/n): ").lower() == '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 
        
        # POPRAWKA RESUME: Bazujemy na completed_configs
        if self.stop_scanning_flag:
            logger.warning(Fore.MAGENTA + "[!!!] Wymuszam zakonczenie puli watkow i zapis postepu...")
            executor.shutdown(wait=False)
            
            # ZMIANA: Wyliczamy pozostałe odejmując te, które FAKTYCZNIE skończyły pracę
            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}.")
        choice = input("Chcesz uzyc zapisanych danych (s) czy wprowadzic nowe (n)? (s/n): ").lower()
        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()
