"""
High-performance IP accessibility tester
"""

import asyncio
import aiohttp
import time
import logging
from typing import Set, Dict, List, Tuple, Optional
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor
import ssl

@dataclass
class TestResult:
    """Result of an IP accessibility test"""
    ip: str
    provider: str
    accessible: bool
    response_time: float
    status_code: Optional[int] = None
    error: Optional[str] = None
    protocol: str = "http"

class AccessibilityTester:
    """High-performance IP accessibility tester"""
    
    def __init__(self, config):
        self.config = config
        self.logger = logging.getLogger("accessibility_tester")
        self.session: Optional[aiohttp.ClientSession] = None
        self.results: List[TestResult] = []
        
    async def __aenter__(self):
        """Async context manager entry"""
        # Create SSL context that doesn't verify certificates for speed
        ssl_context = ssl.create_default_context()
        ssl_context.check_hostname = False
        ssl_context.verify_mode = ssl.CERT_NONE
        
        # Configure connector for high performance
        connector = aiohttp.TCPConnector(
            limit=self.config.performance.concurrent_requests,
            limit_per_host=50,
            ttl_dns_cache=300,
            use_dns_cache=True,
            ssl=ssl_context if not self.config.testing.validate_ssl else None,
            enable_cleanup_closed=True
        )
        
        # Configure timeout
        timeout = aiohttp.ClientTimeout(
            total=self.config.performance.request_timeout,
            connect=2.0,
            sock_read=1.0
        )
        
        # Create session
        self.session = aiohttp.ClientSession(
            connector=connector,
            timeout=timeout,
            headers={'User-Agent': self.config.testing.user_agent},
            skip_auto_headers=['Accept-Encoding']  # Disable compression for speed
        )
        
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """Async context manager exit"""
        if self.session:
            await self.session.close()
    
    async def test_ip_accessibility(self, ip: str, provider: str, test_domain: str) -> TestResult:
        """Test accessibility of a single IP"""
        protocols = self.config.testing.protocols
        
        # Try each protocol until one succeeds
        for protocol in protocols:
            result = await self._test_single_protocol(ip, provider, test_domain, protocol)
            if result.accessible:
                return result
        
        # If all protocols failed, return the last result
        return result
    
    async def _test_single_protocol(self, ip: str, provider: str, test_domain: str, protocol: str) -> TestResult:
        """Test a single IP with a specific protocol"""
        start_time = time.time()
        
        try:
            # Construct URL with IP but use Host header for the domain
            url = f"{protocol}://{ip}/testpage.php"
            headers = {'Host': test_domain} if test_domain else {}
            
            async with self.session.get(
                url,
                headers=headers,
                allow_redirects=self.config.testing.follow_redirects
            ) as response:
                response_time = time.time() - start_time
                
                # Check if response status is OK
                if response.status != 200:
                    return TestResult(
                        ip=ip,
                        provider=provider,
                        accessible=False,
                        response_time=response_time,
                        status_code=response.status,
                        error=f"HTTP {response.status}",
                        protocol=protocol
                    )
                
                # Try to parse JSON response
                try:
                    json_data = await response.json()
                    
                    # Check if the JSON contains the expected test message
                    if json_data.get("test_message") == "testpagegalaxy123":
                        return TestResult(
                            ip=ip,
                            provider=provider,
                            accessible=True,
                            response_time=response_time,
                            status_code=response.status,
                            protocol=protocol
                        )
                    else:
                        return TestResult(
                            ip=ip,
                            provider=provider,
                            accessible=False,
                            response_time=response_time,
                            status_code=response.status,
                            error=f"Invalid test_message: {json_data.get('test_message', 'missing')}",
                            protocol=protocol
                        )
                        
                except Exception as json_error:
                    return TestResult(
                        ip=ip,
                        provider=provider,
                        accessible=False,
                        response_time=response_time,
                        status_code=response.status,
                        error=f"JSON parse error: {str(json_error)}",
                        protocol=protocol
                    )
                
        except asyncio.TimeoutError:
            response_time = time.time() - start_time
            return TestResult(
                ip=ip,
                provider=provider,
                accessible=False,
                response_time=response_time,
                error="Timeout",
                protocol=protocol
            )
        except Exception as e:
            response_time = time.time() - start_time
            return TestResult(
                ip=ip,
                provider=provider,
                accessible=False,
                response_time=response_time,
                error=str(e),
                protocol=protocol
            )
    
    async def test_ip_batch(self, ip_batch: List[Tuple[str, str]], test_domains: Dict[str, str]) -> List[TestResult]:
        """Test a batch of IPs concurrently"""
        tasks = []
        
        for ip, provider in ip_batch:
            test_domain = test_domains.get(provider, "")
            if not test_domain:
                self.logger.warning(f"No test domain configured for provider {provider}")
                continue
            
            task = self.test_ip_accessibility(ip, provider, test_domain)
            tasks.append(task)
        
        # Execute all tests concurrently
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # Filter out exceptions and return valid results
        valid_results = []
        for result in results:
            if isinstance(result, TestResult):
                valid_results.append(result)
            elif isinstance(result, Exception):
                self.logger.error(f"Test error: {result}")
        
        return valid_results
    
    async def test_all_ips(self, provider_ips: Dict[str, Set[str]], test_domains: Dict[str, str]) -> List[TestResult]:
        """Test accessibility of all IPs across all providers"""
        self.logger.info("Starting high-performance IP accessibility testing")
        
        # Flatten all IPs with their providers
        all_ip_provider_pairs = []
        for provider, ips in provider_ips.items():
            for ip in ips:
                all_ip_provider_pairs.append((ip, provider))
        
        total_ips = len(all_ip_provider_pairs)
        self.logger.info(f"Testing {total_ips} IPs across {len(provider_ips)} providers")
        
        # Process IPs in batches for memory efficiency
        batch_size = self.config.performance.batch_size
        all_results = []
        
        for i in range(0, total_ips, batch_size):
            batch = all_ip_provider_pairs[i:i + batch_size]
            batch_num = i // batch_size + 1
            total_batches = (total_ips + batch_size - 1) // batch_size
            
            self.logger.info(f"Processing batch {batch_num}/{total_batches} ({len(batch)} IPs)")
            
            try:
                batch_results = await self.test_ip_batch(batch, test_domains)
                all_results.extend(batch_results)
                
                # Log progress
                accessible_count = sum(1 for r in batch_results if r.accessible)
                self.logger.info(f"Batch {batch_num} completed: {accessible_count}/{len(batch_results)} accessible")
                
            except Exception as e:
                self.logger.error(f"Error processing batch {batch_num}: {e}")
        
        self.results = all_results
        
        # Log final statistics
        total_accessible = sum(1 for r in all_results if r.accessible)
        self.logger.info(f"Testing completed: {total_accessible}/{len(all_results)} IPs accessible")
        
        return all_results
    
    def get_accessible_ips(self) -> Dict[str, List[str]]:
        """Get accessible IPs grouped by provider"""
        accessible_ips = {}
        
        for result in self.results:
            if result.accessible:
                if result.provider not in accessible_ips:
                    accessible_ips[result.provider] = []
                accessible_ips[result.provider].append(result.ip)
        
        return accessible_ips
    
    def get_statistics(self) -> Dict:
        """Get testing statistics"""
        if not self.results:
            return {}
        
        stats = {
            'total_tested': len(self.results),
            'total_accessible': sum(1 for r in self.results if r.accessible),
            'by_provider': {},
            'avg_response_time': sum(r.response_time for r in self.results) / len(self.results),
            'protocols_used': {}
        }
        
        # Statistics by provider
        for result in self.results:
            provider = result.provider
            if provider not in stats['by_provider']:
                stats['by_provider'][provider] = {
                    'tested': 0,
                    'accessible': 0,
                    'success_rate': 0.0
                }
            
            stats['by_provider'][provider]['tested'] += 1
            if result.accessible:
                stats['by_provider'][provider]['accessible'] += 1
        
        # Calculate success rates
        for provider_stats in stats['by_provider'].values():
            if provider_stats['tested'] > 0:
                provider_stats['success_rate'] = provider_stats['accessible'] / provider_stats['tested']
        
        # Protocol statistics
        for result in self.results:
            if result.accessible:
                protocol = result.protocol
                if protocol not in stats['protocols_used']:
                    stats['protocols_used'][protocol] = 0
                stats['protocols_used'][protocol] += 1
        
        return stats