import smtplib
import ssl
import imaplib
import email
import threading
import time
import random
import os
import datetime
import json
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
import socket
from tqdm import tqdm
import re
from html import unescape
import requests
import signal
import sys 

init(autoreset=True)

# Definicja pliku konfiguracyjnego
SETTINGS_FILE = "settings.json"
PROXY_SOURCES_FILE = "proxy_sources.txt"
BLOCKED_DOMAINS_FILE = "blocked-domains.txt"
RESUME_FILE = "resume_smtp_progress.txt"

print(Fore.CYAN + "Ai by Revo - SMTP Checker + Proxy + IMAP Verification [OPTIMIZED]")

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

# ----------------------- Konfiguracja -----------------------
MAX_WORKERS = 150
SEMAPHORE_LIMIT = 30
CONNECT_TIMEOUT = 20         
SMTP_OP_TIMEOUT = 25         
RETRY_COUNT = 1
BACKOFF_BASE = 3
PROXY_CHECK_TIMEOUT = 6
IMAP_POLL_INTERVAL = 11      
IMAP_CONNECTION_TIMEOUT = 35 
PROXY_BLOCK_DURATION = 600 

ORIGINAL_SOCKET = socket.socket

# === NOWA LINIA: DEFINICJA THREAD-LOCAL CONTEXT ===
thread_local = threading.local()

# === ZMIENNE GLOBALNE DLA ROTACJI I BLOKOWANIA PROXY ===
PROXIES = [] 
PROXY_LOCK = threading.Lock()
CURRENT_PROXY_INDEX = 0

BLOCKED_PROXIES = {} 
BLOCKED_LOCK = threading.Lock()

BLOCKED_DOMAINS = set() 

# NOWOŚĆ: Flaga do obsługi Ctrl+C
STOP_SCANNING_FLAG = False

# NOWOŚĆ: Globalny zbiór aktywnych gniazd do szybkiego zamknięcia
ACTIVE_SOCKETS = set()
SOCKET_LOCK = threading.Lock()

# ----------------------- Funkcje DNS/Proxy/Konfiguracja -----------------------

# === FUNKCJA DO ZAMYKANIA AKTYWNYCH GNIAZD ===
def force_close_active_sockets():
    """Próbuje siłowo zamknąć wszystkie aktywne gniazda."""
    closed_count = 0
    with SOCKET_LOCK:
        print(Fore.RED + f"\n[!!!] Rozpoczynam siłowe zamykanie {len(ACTIVE_SOCKETS)} aktywnych gniazd...")
        # Iteracja po kopii, bo będziemy modyfikować zbiór
        for sock in list(ACTIVE_SOCKETS): 
            try:
                # Zamknij gniazdo, co powinno natychmiast przerwać oczekujące operacje IO.
                sock.shutdown(socket.SHUT_RDWR)
                sock.close()
                closed_count += 1
            except Exception:
                pass
        
        ACTIVE_SOCKETS.clear() # Czyścimy zbiór po próbie zamknięcia
    print(Fore.RED + f"[!!!] Siłowo zamknięto {closed_count} gniazd. Pula wątków powinna teraz szybko się zakończyć.")


def signal_handler(sig, frame):
    """Przechwytuje sygnał SIGINT (Ctrl+C) i ustawia flagę zakończenia."""
    global STOP_SCANNING_FLAG
    
    # 1. Ustaw flagę zatrzymania
    STOP_SCANNING_FLAG = True
    print(Fore.MAGENTA + "\n[!!!] SYGNAŁ PRZERWANIA (Ctrl+C) WYKRYTY. KOŃCZĘ SKANOWANIE...")
    
    # 2. Siłowe zamknięcie aktywnych gniazd (Hard Kill)
    force_close_active_sockets()

    # Opóźnienie jest teraz mniej potrzebne, bo main() obsłuży shutdown
    time.sleep(0.5) 
    
    # Wątki powinny teraz szybko się zakończyć z wyjątkiem.


def validate_smtp_host(host, port):
    """Sprawdza, czy host jest rozpoznawalny za pomocą getaddrinfo."""
    try:
        socket.getaddrinfo(host, port, socket.AF_UNSPEC, socket.SOCK_STREAM)
        return True
    except socket.gaierror as e:
        print(Fore.RED + f"[-] ❌ Walidacja DNS dla {host}:{port} nie powiodła się -> [Errno -3] Temporary failure in name resolution")
        return False
    except Exception as e:
        # Prawdopodobnie błąd timeoutu
        return False


def load_blocked_domains(filename):
    """Loads a set of blocked domains from a file."""
    try:
        with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
            domains = {line.strip().lower() for line in f if line.strip()}
            print(Fore.CYAN + f"[i] Wczytano {len(domains)} domen do blokowania z '{filename}'.")
            return domains
    except FileNotFoundError:
        print(Fore.YELLOW + f"[!] Ostrzeżenie: Plik '{filename}' z domenami do blokowania nie został znaleziony. Tworzę pusty zbiór.")
        return set()

def load_proxy_sources(filename):
    """Loads a list of proxy source URLs."""
    try:
        with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
            return [line.strip() for line in f if line.strip() and line.strip().startswith('http')]
    except FileNotFoundError:
        print(Fore.RED + f"[!] Ostrzeżenie: Plik '{filename}' ze źródłami proxy nie został znaleziony.")
        return []

def scrape_and_update_proxies(proxy_file):
    """Scrapes, merges, and cleans the proxy list."""
    print(Fore.CYAN + "[i] Rozpoczynam automatyczną aktualizację i czyszczenie listy proxy...")
    
    sources = load_proxy_sources(PROXY_SOURCES_FILE)
    if not sources:
        print(Fore.RED + "[!] Brak zdefiniowanych źródeł proxy w proxy_sources.txt. Pomijam scrapowanie.")
        return []

    # 1. Wczytanie istniejących proxy
    try:
        existing_proxies = load_proxies(proxy_file)
    except FileNotFoundError:
        existing_proxies = []
    
    # Użycie zbioru do automatycznego usuwania duplikatów
    unique_proxies = set(existing_proxies)
    
    new_proxies_found = 0
    
    print(Fore.CYAN + f"[i] Wczytano {len(existing_proxies)} proxy z {proxy_file}. Scrapuję z {len(sources)} źródeł...")

    # 2. Scrapowanie
    for url in tqdm(sources, desc="Scraping sources"):
        try:
            response = requests.get(url, timeout=10) 
            response.raise_for_status() 
            
            # Regex dla IP:PORT
            ip_port_pattern = r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}:\d+'
            found = re.findall(ip_port_pattern, response.text)
            
            for p in found:
                if p not in unique_proxies:
                    unique_proxies.add(p)
                    new_proxies_found += 1
                    
        except requests.RequestException as e:
            print(Fore.RED + f"[-] Błąd pobierania z {url[:50]}...: {str(e)[:40]}")
        except Exception as e:
            print(Fore.RED + f"[-] Nieoczekiwany błąd podczas scrapowania: {str(e)[:40]}")


    # 3. Zapisanie połączonej i czystej listy
    final_proxies = list(unique_proxies)
    
    with open(proxy_file, 'w', encoding='utf-8') as f:
        for p in final_proxies:
            f.write(p + "\n")
            
    print(Fore.GREEN + f"[+] Zakończono. Dodano {new_proxies_found} nowych proxy. Łącznie unikalnych: {len(final_proxies)}.")
    print(Fore.GREEN + f"[+] Czysta lista została zapisana w pliku {proxy_file}.")

    return final_proxies

def ask_proxy_mode(proxy_list):
    print("\n=== Proxy Mode ===")
    print(f"Wczytano {len(proxy_list)} proxy.")
    print("1) Najpierw sprawdź proxy (wolniej, ale pewność działania) i zapisz działające")
    print("2) Pomijam test — od razu korzystaj z proxy do SMTP (nie zapisuje działających)")

    while True:
        choice = input("Wybierz opcję 1 lub 2: ").strip()
        if choice == "1":
            return "check"
        elif choice == "2":
            return "skip"
        else:
            print("Niepoprawna opcja!")

def load_smtp_servers(filename):
    """
    Wczytuje serwery SMTP z pliku, usuwając duplikaty
    (host, port, user, password).
    """
    unique_servers = set()
    server_list = []
    initial_count = 0
    
    try:
        with open(filename, 'r', encoding='utf-8', errors='ignore') as f:
            for line in f:
                initial_count += 1
                line = line.strip()
                parts = line.split('|')
                if line and len(parts) == 4:
                    # Tworzymy krotkę (tuple) ze wszystkich 4 elementów jako unikalny klucz
                    server_tuple = tuple(parts)
                    if server_tuple not in unique_servers:
                        unique_servers.add(server_tuple)
                        server_list.append(parts)
    except FileNotFoundError:
        # Obsługa FileNotFoundError jest w main, tu tylko logujemy
        pass

    # Usunięto tylko poprawne wpisy 
    removed_count = initial_count - len(server_list)
    
    if removed_count > 0:
        print(Fore.YELLOW + f"[!] Usunięto {removed_count} duplikatów serwerów SMTP z pliku wejściowego.")
        
    return server_list

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

def set_proxy_thread_local(proxy_type, proxy_str):
    """
    Ustawia proxy dla bieżącego wątku. 
    """
    # Używamy thread_local (teraz poprawnie zdefiniowane)
    if not hasattr(thread_local, 'original_socket'):
        thread_local.original_socket = socket.socket

    ip, port = proxy_str.split(':')
    port = int(port)
    types = {
        'http': socks.HTTP,
        'https': socks.HTTP,
        'socks4': socks.SOCKS4,
        'socks5': socks.SOCKS5
    }

    socks.set_default_proxy(types[proxy_type], ip, port, rdns=False)
    socket.socket = socks.socksocket

def reset_socket_thread_local():
    """
    Przywraca domyślne gniazdo wątku do oryginalnej wersji systemowej 
    korzystając z globalnej zmiennej ORIGINAL_SOCKET.
    """
    global ORIGINAL_SOCKET
    # Ustawia socket na globalnie zapisany, oryginalny obiekt gniazda.
    socket.socket = ORIGINAL_SOCKET

def get_next_proxy():
    """Pobiera następne proxy z listy PROXIES, pomijając zablokowane i odblokowując po czasie."""
    global CURRENT_PROXY_INDEX
    global BLOCKED_PROXIES

    now = time.time()

    with BLOCKED_LOCK:
        # 1. Sprawdzenie i odblokowanie starych proxy
        for p in list(BLOCKED_PROXIES.keys()):
            if BLOCKED_PROXIES[p] < now:
                del BLOCKED_PROXIES[p]
                print(Fore.YELLOW + f"[!] Odblokowanie Proxy: {p} usunięte z BLOCKED_PROXIES.")

    with PROXY_LOCK:
        if not PROXIES:
            return None
        
        start_index = CURRENT_PROXY_INDEX
        
        # 2. Szukanie następnego dostępnego proxy w pętli
        while True:
            # Używamy operatora modulo, aby zawinąć indeks do rozmiaru listy PROXIES
            proxy_str = PROXIES[CURRENT_PROXY_INDEX % len(PROXIES)]
            
            # Sprawdź dostępność (tylko sprawdzenie, bez usuwania z listy)
            is_blocked = proxy_str in BLOCKED_PROXIES
                
            # Jeśli jest dostępny, zwracamy go i przesuwamy indeks
            if not is_blocked:
                CURRENT_PROXY_INDEX = (CURRENT_PROXY_INDEX + 1) % len(PROXIES)
                return proxy_str
            
            # Jeśli jest zablokowany, przesuwamy indeks i sprawdzamy, czy zatoczyliśmy koło
            CURRENT_PROXY_INDEX = (CURRENT_PROXY_INDEX + 1) % len(PROXIES)
            if CURRENT_PROXY_INDEX == start_index:
                # Oznacza, że wszystkie proxy są aktualnie zablokowane
                return None


def test_proxy_worker_fast(proxy_type, proxy_str):
    """Ultra fast proxy test using requests library."""
    try:
        if proxy_type in ['http', 'https']:
            proxies = {
                'http': f'http://{proxy_str}',
                'https': f'http://{proxy_str}'
            }
        elif proxy_type == 'socks4':
            proxies = {
                'http': f'socks4://{proxy_str}',
                'https': f'socks4://{proxy_str}'
            }
        elif proxy_type == 'socks5':
            proxies = {
                'http': f'socks5://{proxy_str}',
                'https': f'socks5://{proxy_str}'
            }
        else:
            return None

        response = requests.get(
            'http://www.google.com',
            proxies=proxies,
            timeout=PROXY_CHECK_TIMEOUT,
            allow_redirects=False
        )

        if response.status_code:
            return proxy_str
        return None

    except Exception:
        return None

def test_proxy_ultra_fast(proxy_type, proxy_list):
    """Ultra fast proxy testing."""
    print(Fore.YELLOW + "[i] Using ULTRA FAST mode")
    good = []
    with ThreadPoolExecutor(max_workers=300) as ex:
        futures = {ex.submit(test_proxy_worker_fast, proxy_type, p): p for p in proxy_list}
        for f in tqdm(as_completed(futures), total=len(futures), desc="Testing proxy [FAST]"):
            try:
                r = f.result()
            except Exception:
                r = None
            if r:
                good.append(r)
    return good


def create_smtp_connection(host, port, connect_timeout, op_timeout):
    """
    Create SMTP or SMTP_SSL connection object with 
    configured timeouts and relaxed SSL/TLS verification.
    """
    context = ssl.create_default_context()
    
    # 🚨 POPRAWKA SSL DLA CERTIFICATE_VERIFY_FAILED:
    context.check_hostname = False
    context.verify_mode = ssl.CERT_NONE 

    local_hname = socket.gethostname()

    try:
        if port == 465:
            server = smtplib.SMTP_SSL(host, port, timeout=op_timeout, context=context)
        else:
            server = smtplib.SMTP(host, port, local_hostname=local_hname)
            server.timeout = op_timeout

            if server.sock:
                server.sock.settimeout(op_timeout)

            server.ehlo()
            server.starttls(context=context)
            server.ehlo()

        return server
    except Exception as e:
        raise e

def load_settings():
    """Wczytuje ustawienia z pliku settings.json."""
    if os.path.exists(SETTINGS_FILE):
        with open(SETTINGS_FILE, 'r') as f:
            try:
                return json.load(f)
            except json.JSONDecodeError:
                print(Fore.RED + f"Błąd odczytu pliku {SETTINGS_FILE}. Wprowadź nowe dane.")
                return None
    return None

def save_settings(settings):
    """Zapisuje ustawienia do pliku settings.json."""
    try:
        with open(SETTINGS_FILE, 'w') as f:
            json.dump(settings, f, indent=4)
        print(Fore.GREEN + f"[+] Konfiguracja zapisana w {SETTINGS_FILE}")
    except Exception as e:
        print(Fore.RED + f"Błąd zapisu ustawień: {e}")


def get_user_input(default_settings=None):
    """Pobiera dane od użytkownika lub używa domyślnych."""
    s = default_settings if default_settings else {}

    smtp_file = input(f"Enter SMTP servers filename (aktualnie: {s.get('smtp_file', 'brak')}): ") or s.get('smtp_file')
    threads_str = input(f"Enter number of threads (recommended: 80-150, aktualnie: {s.get('threads', '150')}): ") or str(s.get('threads', 150))
    threads = int(threads_str)
    to_email = input(f"Enter recipient email (aktualnie: {s.get('to_email', 'brak')}): ") or s.get('to_email')
    subject = input(f"Enter email subject (aktualnie: {s.get('subject', 'brak')}): ") or s.get('subject')
    content = input(f"Enter email content (aktualnie: {s.get('content', 'brak')}): ") or s.get('content')
    use_proxy = input(f"Use proxy? (y/n) (aktualnie: {'y' if s.get('use_proxy') else 'n'}): ").lower() == 'y'

    proxy_file = ''
    proxy_type = ''
    auto_update_proxies = False 
    if use_proxy:
        proxy_file = input(f"Enter proxy list filename (aktualnie: {s.get('proxy_filename', 'brak')}): ") or s.get('proxy_filename')
        proxy_type = input(f"Proxy type (http/https/socks4/socks5, aktualnie: {s.get('proxy_type', 'socks5')}): ").lower() or s.get('proxy_type')
        auto_update_proxies_input = input(f"Auto-update proxy (Scraping)? (y/n) (aktualnie: {'y' if s.get('auto_update_proxies') else 'n'}): ").lower()
        auto_update_proxies = auto_update_proxies_input == 'y'


    imap_server = input(f"IMAP server (aktualnie: {s.get('imap_server', 'brak')}): ") or s.get('imap_server')
    imap_user = input(f"IMAP user (aktualnie: {s.get('imap_user', 'brak')}): ") or s.get('imap_user')
    imap_pass = input("IMAP password: ") or s.get('imap_password')
    duration_str = input(f"Minutes to check inbox for replies (aktualnie: {s.get('duration', '5')}): ") or str(s.get('duration', 5))
    duration = int(duration_str)


    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
    }

# === ZMODYFIKOWANA FUNKCJA send_email Z OBSŁUGĄ ACTIVE_SOCKETS ===
def send_email(smtp_info, to_email, subject, content, use_proxy,
               proxy_type, success_queue, semaphore):
    """
    Optimized email sender with thread-local proxy and Retry Count, 
    using Round-Robin proxy selection.
    """
    global STOP_SCANNING_FLAG
    if STOP_SCANNING_FLAG:
        return 

    host, port, user, password = smtp_info
    server = None
    smtp_sock = None # Zmienna do przechowywania gniazda

    try:
        port = int(port)
    except ValueError:
        print(Fore.RED + f"[-] ✗ Błąd: Nieprawidłowy port dla {host} - pomijam.")
        return

    if not validate_smtp_host(host, port):
        return

    unique_id = f"{host}|{port}|{user}|{password}"
    body = content + "\n\n[SMTP-ID] " + unique_id

    message = MIMEMultipart()
    message['From'] = user
    message['To'] = to_email
    message['Subject'] = subject
    message['X-SMTP-ID'] = unique_id
    message.attach(MIMEText(body, 'plain'))
    
    attempts = 0
    while attempts <= RETRY_COUNT: 
        if STOP_SCANNING_FLAG:
            return 
            
        attempts += 1
        chosen_proxy = None

        try:
            if use_proxy and PROXIES:
                chosen_proxy = get_next_proxy()
                if chosen_proxy is None:
                    print(Fore.RED + f"[-] ✗ {user[:35]}... -> Brak dostępnych proxy do rotacji. Pomijam.")
                    break 

            with semaphore:
                if use_proxy and chosen_proxy:
                    set_proxy_thread_local(proxy_type, chosen_proxy)

                # 1. NAWIĄZYWANIE POŁĄCZENIA
                server = create_smtp_connection(host, port, CONNECT_TIMEOUT, SMTP_OP_TIMEOUT)
                
                # === KRYTYCZNE: DODANIE GNIAZDA DO ZBIORU ===
                if server and server.sock:
                    smtp_sock = server.sock
                    with SOCKET_LOCK:
                        ACTIVE_SOCKETS.add(smtp_sock)
                # ============================================

                # 2. LOGIN I WYSYŁKA
                server.login(user, password)
                server.sendmail(user, to_email, message.as_string())

                success_queue.put(unique_id)
                
                # === POPRAWKA: SPRAWDŹ FLAGĘ NATYCHMIAST PO SUKCESIE ===
                if STOP_SCANNING_FLAG:
                    return 
                # =======================================================
                
                if use_proxy and chosen_proxy:
                    print(Fore.GREEN + f"[+] ✓ {user[:35]}... via {chosen_proxy} (Attempt {attempts})")
                else:
                    print(Fore.GREEN + f"[+] ✓ {user[:35]}... (Attempt {attempts})")

                return # Wyjście z funkcji po sukcesie

        except Exception as e:
            # === POPRAWKA: SPRAWDŹ FLAGĘ NATYCHMIAST PO WYJĄTKU ===
            if STOP_SCANNING_FLAG:
                return # Zakończ wątek bez logowania i bez retry
            # =======================================================

            error_msg = str(e)

            if 'Error connecting to SOCKS5 proxy' in error_msg or '0x06: TTL expired' in error_msg:
                error_msg = 'SOCKS proxy error: TTL expired/timed out'
            elif 'Connection reset by peer' in error_msg:
                error_msg = 'Socket error: [Errno 104] Connection reset by peer'
            elif 'Connection closed' in error_msg or 'Connection unexpectedly closed' in error_msg:
                 error_msg = 'SMTP error: Connection closed unexpectedly'
            elif 'SSL/TLS handshake failed' in error_msg:
                 error_msg = 'SSL/TLS Handshake failed'
            
            # Wątki już przerwane przez flagę nie powinny dojść do tego miejsca, 
            # ale zostawiam ten kod w razie gdyby jakiś wyjątkowy błąd się przedarł
            elif 'timeout' in error_msg or 'Connection refused' in error_msg or 'closed' in error_msg or 'reset' in error_msg:
                if STOP_SCANNING_FLAG:
                    return 

            if attempts > RETRY_COUNT:
                print(Fore.RED + f"[-] ✗ {user[:35]}... -> {error_msg[:70]} (All {attempts} attempts failed)")

                if use_proxy and chosen_proxy:
                    with BLOCKED_LOCK:
                        if chosen_proxy not in BLOCKED_PROXIES:
                            BLOCKED_PROXIES[chosen_proxy] = time.time() + PROXY_BLOCK_DURATION
                            print(Fore.YELLOW + f"[!] Blokada Proxy: {chosen_proxy} dodane do BLOCKED_PROXIES. Odblokowanie za {PROXY_BLOCK_DURATION // 60} min.")
            else:
                print(Fore.YELLOW + f"[-] ⚠ {user[:35]}... -> {error_msg[:70]} (Attempt {attempts}/{RETRY_COUNT + 1} failed, retrying...)")
                time.sleep(BACKOFF_BASE)

        finally:
            # === KRYTYCZNE: USUNIĘCIE GNIAZDA ZE ZBIORU ===
            if smtp_sock:
                with SOCKET_LOCK:
                    if smtp_sock in ACTIVE_SOCKETS:
                        ACTIVE_SOCKETS.remove(smtp_sock)
                
            if use_proxy:
                reset_socket_thread_local() 

            if server:
                try:
                    server.quit() 
                except:
                    try:
                        server.close()
                    except:
                        if hasattr(server, 'sock') and server.sock:
                            try:
                                server.sock.settimeout(1) 
                                server.sock.close()
                            except:
                                pass
                del server 

    return

# === FUNKCJA imap_check ===
def imap_check(server, user, password, subject, duration_minutes, success_queue):
    """
    Stabilny IMAP check z mechanizmem ponownych prób połączenia, odpornością na błędy gniazd 
    i sprawdzaniem folderów INBOX/SPAM/JUNK.
    """
    MAX_IMAP_RETRIES = 3

    if not validate_smtp_host(server, 993): 
        print(Fore.RED + f"[IMAP ERROR] Nie można rozwiązać nazwy hosta IMAP: {server}. Koniec sprawdzania IMAP.")
        return

    id_pattern = re.compile(r'\[SMTP[-_\s]?ID\]\s*([^\s]+(?:\|[^\s]+)*)', re.IGNORECASE)
    found_ids = set()
    end_time = time.time() + duration_minutes * 60

    print(Fore.CYAN + "[i] IMAP monitor running...")

    while time.time() < end_time:
        mail = None
        retry_count = 0
        connected = False

        # === BLOK 1: PĘTLA PONOWNYCH PRÓB POŁĄCZENIA I LOGOWANIA ===
        while retry_count <= MAX_IMAP_RETRIES:
            try:
                mail = imaplib.IMAP4_SSL(server, timeout=IMAP_CONNECTION_TIMEOUT)
                
                if mail.sock:
                    mail.sock.settimeout(IMAP_CONNECTION_TIMEOUT)
                    
                mail.login(user, password)
                
                folders_to_check = ["INBOX", "Inbox", "SPAM", "Spam", "JUNK", "Junk"]
                selected_folder = None

                for folder in folders_to_check:
                    try:
                        mail.select(folder, readonly=True)
                        selected_folder = folder
                        break
                    except imaplib.IMAP4.error:
                        continue 

                if selected_folder:
                    print(Fore.CYAN + f"[i] Połączono. Sprawdzam folder: {selected_folder}")
                else:
                    raise Exception("Nie można wybrać żadnego folderu IMAP (INBOX, Spam, Junk).")
                
                connected = True
                break 

            except Exception as e:
                error_msg = str(e)
                
                if mail:
                    try: mail.logout()
                    except: pass
                
                print(Fore.RED + f"[IMAP CONNECTION ERROR] {error_msg} (Próba {retry_count + 1}/{MAX_IMAP_RETRIES + 1})")
                
                time.sleep(2 * retry_count + 1)
                retry_count += 1
        
        if not connected:
            print(Fore.RED + "[IMAP ERROR] Nie udało się nawiązać połączenia po wszystkich próbach. Czekam na następny cykl.")
            time.sleep(IMAP_POLL_INTERVAL)
            continue 

        # === BLOK 2: WYSZUKIWANIE I POBIERANIE WIADOMOŚCI ===
        # === FIX: INICJALIZACJA ZMIENNEJ PRZED BLOKIEM TRY ===
        fetch_error_occurred = False 
        # ====================================================
        
        try:
            today = datetime.date.today().strftime("%d-%b-%Y")
            
            # Wyszukujemy tylko maile z dzisiaj, które pasują do tematu (Zgodnie z Twoją prośbą)
            search_query = f'(SINCE "{today}" SUBJECT "{subject}")' 
            result, data = mail.search(None, search_query)
            
            scanned = 0
            if result == 'OK' and data and data[0]:
                ids = data[0].split()
                scanned = len(ids)

                print(Fore.CYAN + f"[i] Znaleziono {scanned} wiadomości z dzisiaj (dopasowane do tematu), sprawdzam...")
                
                # Zmienna fetch_error_occurred jest już zdefiniowana jako False na zewnątrz try.

                for msg_id in ids:
                    try:
                        # POBIERANIE TYLKO NAGŁÓWKÓW
                        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)

                        msg_subject = msg_header.get('Subject', '')
                        # Ponowne sprawdzenie tematu, choć serwer już to zrobił (dla bezpieczeństwa)
                        if subject.lower() not in msg_subject.lower():
                            continue

                        smtp_id = msg_header.get('X-SMTP-ID')
                        if smtp_id:
                            smtp_id = smtp_id.strip()
                        else:
                            # Jeśli brak X-SMTP-ID, pobieramy pełną treść (RFC822) i szukamy [SMTP-ID] w treści
                            res2_full, msg_data_full = mail.fetch(msg_id, '(RFC822)')
                            
                            if res2_full != 'OK' or not msg_data_full or not msg_data_full[0]:
                                continue
                            raw_full = msg_data_full[0][1]
                            msg = email.message_from_bytes(raw_full)
                            
                            body_text = ''
                            for part in msg.walk():
                                ctype = part.get_content_type()
                                disp = str(part.get('Content-Disposition') or '')
                                if part.is_multipart() or 'attachment' in disp.lower():
                                    continue
                                
                                try:
                                    payload = part.get_payload(decode=True)
                                    if payload:
                                        charset = part.get_content_charset() or 'utf-8'
                                        text = payload.decode(charset, errors='ignore')
                                        body_text += '\n' + text
                                except:
                                    pass

                            if body_text:
                                m = id_pattern.search(body_text)
                                if m:
                                    smtp_id = m.group(1).strip()
                        
                        # Ostateczne potwierdzenie (musi mieć unikalny ID)
                        if smtp_id and smtp_id not in found_ids:
                            print(Fore.YELLOW + f"[+] Delivered: {smtp_id}")
                            found_ids.add(smtp_id)

                    except Exception as e_msg:
                        print(Fore.RED + f"[IMAP FETCH ERROR] {e_msg}")
                        fetch_error_occurred = True
                        break 

                if scanned > 0:
                    print(Fore.CYAN + f"[i] Sprawdzono {scanned} wiadomości, znaleziono {len(found_ids)} potwierdzonych SMTP")
                else:
                    print(Fore.CYAN + "[i] Nie znaleziono żadnych wiadomości z dzisiaj pasujących do tematu. Czekam...")

        except Exception as e:
            print(Fore.RED + f"[IMAP ERROR DURING SEARCH] {str(e)}")
            fetch_error_occurred = True
            
        finally:
            if mail:
                try: mail.logout()
                except: pass
        
        # To jest teraz bezpieczne, ponieważ zmienna jest zawsze zdefiniowana
        if fetch_error_occurred: 
            print(Fore.YELLOW + "[i] Błąd gniazda podczas sprawdzania, nastąpi próba ponownego połączenia w kolejnym cyklu.")
            
        time.sleep(IMAP_POLL_INTERVAL)

    print(Fore.CYAN + f"[i] Czyszczenie kolejki i zapisywanie {len(found_ids)} potwierdzonych ID...")
    try:
        while True:
            success_queue.get_nowait()
    except Exception:
        pass

    for sid in found_ids:
        success_queue.put(sid)

    print(Fore.GREEN + f"[+] IMAP monitoring finished. Found {len(found_ids)} confirmed deliveries.")


# ----------------------- Main -----------------------
def main():
    global PROXIES 
    global BLOCKED_DOMAINS 
    global STOP_SCANNING_FLAG 

    print(Fore.CYAN + "\n" + "="*60)
    print(Fore.CYAN + " Fast & Reliable SMTP Checker")
    print(Fore.CYAN + " Optimized for speed while maintaining reliability")
    print(Fore.CYAN + "="*60 + "\n")

    # === REJESTRACJA SIGNAL HANDLER dla Ctrl+C ===
    signal.signal(signal.SIGINT, signal_handler)

    current_settings = load_settings()
    settings = {}
    
    if current_settings:
        print(Fore.CYAN + f"[i] Znaleziono plik {SETTINGS_FILE}.")
        choice = input("Chcesz użyć zapisanych danych (s) czy wprowadzić nowe (n)? (s/n): ").lower()
        if choice == 's':
            settings = current_settings
            print(Fore.GREEN + "[+] Używam zapisanej konfiguracji.")
        else:
            settings = get_user_input(current_settings)
            save_settings(settings)
    else:
        settings = get_user_input()
        save_settings(settings)

    # Wczytanie ustawień
    smtp_file = settings['smtp_file']
    threads = settings['threads']
    to_email = settings['to_email']
    subject = settings['subject']
    content = settings['content']
    use_proxy = settings['use_proxy']
    proxy_file = settings['proxy_filename']
    proxy_type = settings['proxy_type']
    auto_update_proxies = settings.get('auto_update_proxies', False)
    imap_server = settings['imap_server']
    imap_user = settings['imap_user']
    imap_pass = settings['imap_password']
    duration = settings['duration']

    # === Wczytywanie Domen do Blokowania ===
    BLOCKED_DOMAINS = load_blocked_domains(BLOCKED_DOMAINS_FILE)
    
    # === LOGIKA WCZYTYWANIA PROXY ZE SCRAPINGIEM ===
    if use_proxy:
        proxy_list_raw = []
        if auto_update_proxies:
            proxy_list_raw = scrape_and_update_proxies(proxy_file)
        else:
            try:
                proxy_list_raw = load_proxies(proxy_file)
            except FileNotFoundError:
                print(Fore.RED + f"Błąd: Plik proxy '{proxy_file}' nie został znaleziony.")
                return

        print(Fore.CYAN + f"[i] Wczytano (lub zaktualizowano) proxy: {len(proxy_list_raw)}")

        mode = ask_proxy_mode(proxy_list_raw)

        if mode == "check":
            print(Fore.CYAN + "[i] Testing proxy (ULTRA FAST mode)...")
            PROXIES = test_proxy_ultra_fast(proxy_type, proxy_list_raw) 
            
            print(Fore.CYAN + f"[i] Working proxy: {len(PROXIES)}")
            
            if PROXIES:
                with open("good_proxies.txt", "w", encoding="utf-8") as f:
                    for p in PROXIES:
                        f.write(p + "\n")
                print(Fore.GREEN + f"[+] Zapisano {len(PROXIES)} działających proxy do good_proxies.txt")
            
            if not PROXIES:
                print(Fore.RED + "Brak działających proxy. Kończę.")
                return
        else:
            print(Fore.YELLOW + "[i] Pomijam test proxy — używam pełnej listy.")
            PROXIES = proxy_list_raw 
    
    # === LOGIKA WZNOWIENIA (RESUME) I Wczytanie Serwerów SMTP ===
    smtp_servers_raw = []
    
    if os.path.exists(RESUME_FILE):
        print(Fore.YELLOW + f"[i] Znaleziono plik wznowienia: {RESUME_FILE}.")
        choice = input("Czy chcesz wznowić skanowanie z tego pliku (y/n)? ").lower()
        if choice == 'y':
            try:
                smtp_servers_raw = load_smtp_servers(RESUME_FILE) 
                print(Fore.GREEN + f"[+] Wznowiono skanowanie. Wczytano {len(smtp_servers_raw)} serwerów do przetworzenia.")
            except Exception:
                print(Fore.RED + "Błąd: Plik wznowienia nie został wczytany. Rozpoczynam od pliku głównego.")
                smtp_servers_raw = load_smtp_servers(smtp_file) 
        else:
            smtp_servers_raw = load_smtp_servers(smtp_file) 
    else:
        try:
            smtp_servers_raw = load_smtp_servers(smtp_file)
        except FileNotFoundError:
            print(Fore.RED + f"Błąd: Plik SMTP '{smtp_file}' nie został znaleziony.")
            return

    print(Fore.CYAN + f"\n[i] Wczytano {len(smtp_servers_raw)} serwerów SMTP (po usunięciu duplikatów).")
    
    # === FILTROWANIE BLOKOWANYCH DOMEN ===
    temp_list = []
    blocked_count = 0
    for host, port, user, password in smtp_servers_raw:
        domain = user.split('@')[-1].lower()
        if domain in BLOCKED_DOMAINS:
            blocked_count += 1
        else:
            temp_list.append([host, port, user, password]) 

    smtp_servers_raw = temp_list 
    print(Fore.GREEN + f"[+] Zablokowano i pominięto {blocked_count} serwerów na podstawie listy domen.")
    
    
    # === WSTĘPNA WALIDACJA DNS dla SMTP ===
    print(Fore.CYAN + "[i] Rozpoczynam wstępną walidację DNS hostów SMTP (bez użycia proxy)...")
    
    valid_smtp_servers = []
    
    with ThreadPoolExecutor(max_workers=threads) as executor:
        futures = {}
        for smtp_info in smtp_servers_raw:
            host, port, user, password = smtp_info
            try:
                port_int = int(port)
            except ValueError:
                continue 
            
            future = executor.submit(validate_smtp_host, host, port_int)
            futures[future] = tuple(smtp_info) # Kluczem jest obiekt Future, wartością krotka SMTP
            
        for future in tqdm(as_completed(futures), total=len(futures), desc="Walidacja DNS"):
            if future.result():
                valid_smtp_servers.append(list(futures[future]))
            
    smtp_servers = valid_smtp_servers 
    
    print(Fore.GREEN + f"[+] Walidacja DNS zakończona. {len(smtp_servers)}/{len(smtp_servers_raw)} hostów jest poprawnych.")
    if not smtp_servers:
        print(Fore.RED + "Brak hostów SMTP z poprawnym rekordem DNS. Kończę.")
        return

    success_queue = Queue()
    semaphore = threading.Semaphore(SEMAPHORE_LIMIT)

    print(Fore.CYAN + f"\n[i] Loaded {len(smtp_servers)} SMTP servers (Po walidacji DNS)")
    print(Fore.YELLOW + f"[i] Starting with {threads} parallel threads")
    print(Fore.GREEN + f"[i] Semaphore limit: {SEMAPHORE_LIMIT} (Ważne dla stabilności DNS)\n")

    # Używamy słownika dla łatwego usuwania po ukończeniu
    smtp_to_process = {tuple(s): s for s in smtp_servers}

    # === POPRAWIONY BLOK WYKONYWANIA ZADAŃ (BEZ BLOKUJĄCEGO AS_COMPLETED) ===
    executor = ThreadPoolExecutor(max_workers=threads)
    futures_dict = {executor.submit(
                        send_email,
                        smtp_info,
                        to_email,
                        subject,
                        content,
                        use_proxy,
                        proxy_type,
                        success_queue,
                        semaphore
                    ): tuple(smtp_info) for smtp_info in smtp_servers}

    progress_bar = tqdm(total=len(futures_dict), desc="Checking SMTP")
    completed_count = 0
    
    # Pętla sprawdzająca, czy zadania się zakończyły, ale nie blokująca głównego wątku
    while completed_count < len(futures_dict) and not STOP_SCANNING_FLAG:
        
        # Oblicz, ile zadań zakończyło się od ostatniego sprawdzenia
        newly_completed = sum(1 for f in futures_dict.keys() if f.done()) - completed_count
        
        if newly_completed > 0:
            progress_bar.update(newly_completed)
            completed_count += newly_completed

        time.sleep(0.1) # Krótki sen, aby nie obciążać CPU, ale szybko reagować na Ctrl+C

    # === ZAMKNIĘCIE PASKA POSTĘPU PRZED LOGOWANIEM ===
    progress_bar.close() 

    # === KRYTYCZNE: WYMUSZONE ZAMKNIĘCIE EXECUTORA PO WYKRYCIU PRZERWANIA ===
    if STOP_SCANNING_FLAG:
        print(Fore.MAGENTA + "[!!!] Wymuszam natychmiastowe zakończenie puli wątków...")
        # wait=False - nie czekamy na zakończenie wątków
        executor.shutdown(wait=False) 
        
    # === ZAPISYWANIE POSTĘPU I PRZETWARZANIE WYNIKÓW (NAPRAWIONY BLOK) ===
    
    # 1. Zbieranie kluczy ZAKOŃCZONYCH zadań
    completed_keys_set = {k for f, k in futures_dict.items() if f.done()} 
        
    for key in completed_keys_set:
        if key in smtp_to_process:
            del smtp_to_process[key]

    # 2. LOGIKA ZAPISU / WZNOWIENIA (Checkpointing)
    if smtp_to_process:
        # Ten blok uruchamia się tylko, jeśli są nieprzetworzone serwery (np. po Ctrl+C)
        print(Fore.MAGENTA + f"[!!!] Zapisywanie {len(smtp_to_process)} nieprzetworzonych serwerów do {RESUME_FILE}...")
        with open(RESUME_FILE, 'w', encoding='utf-8') as f:
            for smtp_info in smtp_to_process.values():
                f.write("|".join(smtp_info) + "\n")
        print(Fore.MAGENTA + f"[!!!] Zapis zakończony. Przechodzę do IMAP Check...")
    else:
        # Ten blok uruchamia się, jeśli wszystkie zadania się zakończyły (normalnie lub po Ctrl+C)
        print(Fore.MAGENTA + "[!!!] Wszystkie zadania zostały ukończone. Kontynuuję do IMAP Check.")

        # 3. CZYSZCZENIE PLIKU WZNOWIENIA (tylko, jeśli skończono normalnie)
        if os.path.exists(RESUME_FILE):
            try:
                os.remove(RESUME_FILE)
                print(Fore.CYAN + f"[i] Skanowanie zakończone normalnie. Usunięto plik wznowienia: {RESUME_FILE}.")
            except Exception as e:
                print(Fore.YELLOW + f"[!] Ostrzeżenie: Nie udało się usunąć pliku wznowienia: {e}")

    # Koniec bloku po pętli. Dalej program przechodzi do IMAP.

    print(Fore.CYAN + "\n[i] Wysyłka zakończona. Rozpoczynam IMAP verification.")

    reset_socket_thread_local()

    imap_check(imap_server, imap_user, imap_pass, subject, duration, success_queue) 

    good = set()
    while not success_queue.empty():
        good.add(success_queue.get())

    with open("good.txt", "w", encoding="utf-8") as f:
        for s in good:
            f.write(s + "\n")

    print(Fore.GREEN + f"\n[+] Zapisano {len(good)} działających SMTP do good.txt")
    print(Fore.CYAN + "="*60)
    print(Fore.CYAN + "Ai by Revo - koniec programu")
    print(Fore.CYAN + "="*60)

if __name__ == "__main__":
    main()
