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

# 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"
PROXIES_TESTED_FILE = "proxies_tested.txt" 

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

# Stałe domyślne
MAX_WORKERS_DEFAULT = 150
SEMAPHORE_LIMIT_DEFAULT = 30
SMTP_OP_TIMEOUT_DEFAULT = 25
RETRY_COUNT_DEFAULT = 1
BACKOFF_BASE_DEFAULT = 4
PROXY_CHECK_TIMEOUT_DEFAULT = 6
IMAP_POLL_INTERVAL_DEFAULT = 15
IMAP_CONNECTION_TIMEOUT_DEFAULT = 35
PROXY_BLOCK_DURATION_DEFAULT = 600

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

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

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

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

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

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

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

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

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

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

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

# ====================== 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.executor = None 

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

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


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

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

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

    # --- ZARZĄDZANIE DANYMI ---

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

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

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

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

    # --- ZARZĄDZANIE PROXY ---

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

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

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

    def test_proxy_worker_fast(self, proxy_type, proxy_str):
        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 = []
        MAX_PROXY_WORKERS = 300 
        
        with ThreadPoolExecutor(max_workers=MAX_PROXY_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)]
                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:
            # Połączenie bezpośrednie (bez proxy)
            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()
        context.check_hostname = False
        context.verify_mode = ssl.CERT_NONE
        
        BUFSIZE = 8192
        
        port = int(port)
        sock = None
        server = None
        
        try:
            # Uzywa _create_raw_socket_with_proxy, ktory uzywa albo proxy, albo bezposrednie polaczenie
            sock = self._create_raw_socket_with_proxy(host, port, proxy_str, proxy_type, self.smtp_timeout)
            
            with self.socket_lock:
                self.active_sockets.add(sock)

            if port == 465:
                # SSL (465)
                # Używamy server_hostname dla SSL/TLS (SNI)
                ssl_sock = context.wrap_socket(sock, server_hostname=host)
                server = smtplib.SMTP_SSL(timeout=self.smtp_timeout, context=context)
                server.sock = ssl_sock 
                server.file = ssl_sock.makefile('rb', BUFSIZE) 
                
            else:
                # Standardowy SMTP (25, 587)
                server = smtplib.SMTP(timeout=self.smtp_timeout)
                server.sock = sock
                server.file = sock.makefile('rb', BUFSIZE) 
                
                # === KRYTYCZNA POPRAWKA: Wymuszenie odczytu początkowego powitania serwera (kod 220) ===
                # Zapobiega problemom, gdy smtplib.SMTP próbuje czytać powitanie na gnieździe proxy.
                status, msg = server.getreply() 
                if status != 220:
                    raise smtplib.SMTPConnectError(status, msg)
                # ======================================================================================

                server.ehlo()
                
                if server.has_extn('starttls'):
                    # === KRYTYCZNA POPRAWKA: Usunięcie 'host=host' dla kompatybilności z Python < 3.12 ===
                    server.starttls(context=context) # Usunieto host=host
                    server.ehlo()
                else:
                    if self.is_smtp_phase_active:
                        logger.warning(f"[!] {host}:{port} - STARTTLS niedostepne. Proba bez szyfrowania.")

            return server
        except Exception as e:
            if self.stop_scanning_flag: return None
            
            if self.is_smtp_phase_active:
                if "certificate" in str(e).lower() or "hostname" in str(e).lower() or "wrong version number" in str(e).lower():
                    logger.warning(f"[!] SSL error dla {host}:{port} - problem z certyfikatem.")
            
            # Wystarczy podnieść wyjątek
            raise e
        finally:
            if sock:
                with self.socket_lock:
                    self.active_sockets.discard(sock)

    # --- SEND EMAIL ---

    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
            
            log_proxy_info = ""
            
            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_proxy_info = f" via {proxy}"
                
                log_msg = f"[{user[:35]}...] Proba {attempts}/{self.retry_count+1}{log_proxy_info}"
                
                if self.is_smtp_phase_active:
                    logger.info(log_msg)

                with self.semaphore:
                    # Używamy proxy tylko jeśli 'use_proxy' jest True i znaleźliśmy adres proxy
                    server = self.create_smtp_connection_with_proxy(host, port, proxy if use_proxy else None, proxy_type)
                    if server is None: return

                    server.login(user, password)
                    server.sendmail(user, to_email, msg.as_string())
                    
                    success_queue.put(unique_id)
                    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
                
                if self.is_smtp_phase_active:
                    error_msg = str(e)[:70]
                    # Dodanie informacji o używanym adresie/proxy przy błędzie
                    conn_source = proxy if use_proxy and proxy else "Wlasny IP"
                    logger.error(f"{Fore.RED}[-] Blad dla {user[:35]}... (Proba {attempts}, Zrodlo: {conn_source}): {error_msg}")
                
                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)
                
                # Blokujemy proxy tylko, jeśli używaliśmy proxy
                if use_proxy and proxy:
                    with self.blocked_lock:
                        self.blocked_proxies[proxy] = time.time() + PROXY_BLOCK_DURATION_DEFAULT
            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):
        """Monitoruje IMAP w poszukiwaniu wiadomości potwierdzającej dostarczenie."""
        MAX_IMAP_RETRIES = 3
        if not validate_smtp_host(server, 993):
            logger.error(f"[IMAP ERROR] Nie mozna rozwiazac nazwy hosta IMAP: {server}")
            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: 
                                    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)

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


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

            self.proxies = self.test_proxy_ultra_fast(self.settings['proxy_type'], proxy_list_raw) if mode == "check" else proxy_list_raw
            
            if mode == "check" and self.proxies:
                 with open(PROXIES_TESTED_FILE, 'w', encoding='utf-8') as f:
                     f.write('\n'.join(self.proxies) + '\n')
                 logger.info(Fore.GREEN + f"[+] Zapisano {len(self.proxies)} przetestowanych proxy do {PROXIES_TESTED_FILE}")
            
            if not self.proxies and self.settings['use_proxy']:
                logger.error(Fore.RED + "Brak dzialajacych proxy. Koncze.")
                return
            
            if self.proxies:
                 logger.info(Fore.GREEN + f"[+] Aktywnych proxy: {len(self.proxies)}")

        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 względu na zablokowane domeny.")
        smtp_servers_raw = temp_list

        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()
        self.executor = ThreadPoolExecutor(max_workers=self.settings['threads'])
        futures_dict = {
            self.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...")
            self.executor.shutdown(wait=False) 
            
            remaining = [info for f, info in futures_dict.items() if not f.done()]
            
            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:
            self.executor.shutdown(wait=True) 
            
            if os.path.exists(RESUME_FILE):
                try:
                    os.remove(RESUME_FILE)
                    logger.info(Fore.CYAN + f"[i] Skanowanie zakończone normalnie. Usunięto plik wznowienia: {RESUME_FILE}.")
                except Exception as e:
                    logger.warning(f"[!] Ostrzeżenie: Nie udało się usunąć pliku wznowienia: {e}")

        self.executor = None 

        # 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 = []
        for info in valid_smtp_servers:
            if "|".join(info) in good or tuple(info) in good:
                final_good.append("|".join(info))

        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)

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

    # Gwarantowane wyjście z programu, nawet jeśli wiszą nie-daemonowe wątki
    sys.exit(0)

if __name__ == "__main__":
    main()
