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 
        self.completed_smtp = set()
        self.completed_lock = threading.Lock()
        self.proxy_in_use = set()
        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

    def test_proxy_worker_fast(self, proxy_type, proxy_str):
        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]: 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)...")
        good = []
        # Zgodnie z sugestiami optymalizacyjnymi, lepiej zredukować ten ThreadPool
        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): 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)]

            # proxy wolne + nie zablokowane
            if proxy not in self.blocked_proxies and proxy not in self.proxy_in_use:
                self.proxy_in_use.add(proxy)
                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 (TRANSPLANTOWANE Z DROGA_A_CLEAN) ---

    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 (unchanged behaviour) ===
        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 ===
        # PySocks nie obsługuje IPv6 - musimy wymusić resolucję IPv4
        try:
            addr_info = socket.getaddrinfo(host, port, socket.AF_INET, socket.SOCK_STREAM)
            if not addr_info:
                raise Exception("Brak adresu IPv4 dla hosta")
            # Używamy pierwszego dostępnego IPv4
            ipv4_host = addr_info[0][4][0]
        except Exception as e:
            raise Exception(f"IPv6-only host (PySocks nie obsluguje): {host}")

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())

                # NIE DODAJEMY DO success_queue - tylko IMAP dodaje po weryfikacji doręczenia
                if self.is_smtp_phase_active:
                    logger.info(f"{Fore.GREEN}[+] Sukces: {user} ({unique_id})")

                # SMTP zakończone logicznie – NIE retry, NIE blokuj proxy
                with self.completed_lock:
                    self.completed_smtp.add(unique_id)

                return

        except Exception as e:
            if self.stop_scanning_flag:
                return

            if self.is_smtp_phase_active:
                error_msg = str(e)[:120]
                logger.error(f"{Fore.RED}[-] Blad dla {user[:35]}... (Proba {attempts}): {error_msg}")

            # BLOKUJ PROXY TYLKO ZA BŁĘDY TRANSPORTOWE
            transport_errors = (
                socket.timeout,
                TimeoutError,
                socks.ProxyError,
                ConnectionRefusedError,
                ssl.SSLError,
                OSError,
            )

            if proxy and isinstance(e, transport_errors):
                with self.blocked_lock:
                    self.blocked_proxies[proxy] = time.time() + PROXY_BLOCK_DURATION_DEFAULT
            else:
                # Błąd logiczny SMTP (AUTH / relay / policy) – NIE retry, NIE blokuj proxy
                with self.completed_lock:
                    self.completed_smtp.add(unique_id)
                return

            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)

        finally:
            if proxy:
                with self.proxy_lock:
                    self.proxy_in_use.discard(proxy)

            if server:
                try:
                    # Upewnienie się, że socket jest usunięty po zamknięciu połączenia
                    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):
        """Monitoruje IMAP w poszukiwaniu wiadomości potwierdzającej dostarczenie."""
        MAX_IMAP_RETRIES = 3
        
        # KRYTYCZNE: Upewniamy się, że IMAP NIE używa proxy
        # Przywracamy oryginalny socket przed połączeniem IMAP
        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:
                # Domyślna lista folderów do sprawdzenia
                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()
                        
                        # POZOSTAWIAM TWOJ REGEKS, ABY NIE WPROWADZAC INNYCH ZMIAN
                        match = re.search(r'\"([^\"]+)\"$', f_str)
                        if match:
                            folder_name = match.group(1).replace('"', '')
                            
                            # Dodajemy foldery, które nie są spamem/śmieciami (sprawdzamy też wielkość liter)
                            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)

                # Upewniamy się, że INBOX jest zawsze sprawdzany jako pierwszy
                if 'INBOX' not in target_folders:
                    target_folders.insert(0, 'INBOX')
                
                # Usuwamy duplikaty
                target_folders = list(dict.fromkeys(target_folders))

                today = datetime.date.today().strftime("%d-%b-%Y")
                scanned = 0
                
                for folder in target_folders:
                    try:
                        # Upewniamy się, że folder jest poprawnie cytowany w poleceniu SELECT
                        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:
                                    # Pobieranie tylko nagłówków (Subject, X-SMTP-ID)
                                    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
                                    
                                    # LINIA Z BŁĘDEM WCIŚCIA - POPRAWIONA
                                    raw_header = msg_data[0][1] 
                                    msg_header = email.message_from_bytes(raw_header)
                                    
                                    # Filtrowanie po temacie (jeśli go używamy)
                                    if subject.lower() not in msg_header.get('Subject', '').lower(): continue
                                    
                                    smtp_id = msg_header.get('X-SMTP-ID')
                                    
                                    # Pobieranie pełnej treści, jeśli X-SMTP-ID jest puste
                                    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:
                                                            # U-8 -> UTF-8 POPRAWIONE
                                                            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: 
                                    logger.error(f"[IMAP FETCH ERROR] {str(e)[:50]}")
                                    pass
                    except Exception as e:
                        logger.error(f"[IMAP FOLDER ERROR] ({folder}): {str(e)[:50]}")
                        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:
                logger.error(f"[IMAP OPERATIONAL ERROR] {str(e)[:70]}")

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

            time.sleep(IMAP_POLL_INTERVAL_DEFAULT)

        # Ostateczne dodanie wszystkich znalezionych ID do kolejki
        for sid in found_ids: 
            success_queue.put(sid) 
        logger.info(Fore.GREEN + f"[+] IMAP monitoring finished. Found {len(found_ids)} confirmed deliveries.")
        
        # Przywracamy oryginalny socket na koniec
        socket.socket = original_socket


    # --- GŁÓWNA METODA URUCHAMIAJĄCA (SMTP + IMAP) ---

    def run(self):
        """Metoda uruchamiająca cały proces sprawdzania (SMTP + IMAP lub tylko IMAP)."""
        
        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)
        
        # ... (Logika ładowania i testowania Proxy) ...
        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

            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 = []
        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'])

        # Filtrowanie zablokowanych domen
        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

        # Główna pętla monitorująca
        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()
        
        # 3. Zapisywanie postępu i czyszczenie
        
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)  # Przerwanie oczekujących zadań

    # Zapis TYLKO tych SMTP, które NIE zakończyły się logicznie
    remaining = [
        info for info in smtp_servers_to_process.values()
        if "|".join(info) not in self.completed_smtp
    ]

    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)  # Zakończenie normalne
    if os.path.exists(RESUME_FILE):
        try:
            os.remove(RESUME_FILE)
            logger.info(
                Fore.CYAN
                + f"[i] Skanowanie zakonczone normalnie. Usunieto plik wznowienia: {RESUME_FILE}."
            )
        except Exception as e:
            logger.warning(
                f"[!] Ostrzezenie: Nie udalo sie usunac pliku wznowienia: {e}"
            )

        # 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 - TYLKO te potwierdzone przez IMAP
        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)

    # --- NOWA METODA: TYLKO IMAP ---

    def run_imap_only(self):
        """Metoda uruchamiająca tylko weryfikację IMAP."""
        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()
        
        # 1. Weryfikacja IMAP
        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) 

        # 2. Zapis końcowy
        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')
        
        logger.info(Fore.GREEN + f"\n[+] Zapisano {len(final_good)} potwierdzonych dostaw do good.txt")
        print(Fore.CYAN + "="*60)
        print(Fore.CYAN + "Ai by Revo - koniec programu")
        print(Fore.CYAN + "="*60)


# ====================== 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)

    # Uruchomienie głównego procesu
    checker = SmtpChecker(settings)
    
    try:
        checker.run()
    except KeyboardInterrupt:
        logger.warning(Fore.MAGENTA + "[!!!] Przechwycono KeyboardInterrupt w main. Wychodzenie...")
        pass


if __name__ == "__main__":
    main()
