"""
Async I/O implementation for network scanning in CHRONOS.

This module provides asynchronous network operations for efficient
concurrent scanning of multiple targets and services.
"""

import asyncio
import aiohttp
import socket
import logging
from typing import List, Dict, Optional, Callable, Any, Coroutine
from dataclasses import dataclass
from datetime import datetime, timedelta
import time

logger = logging.getLogger(__name__)


@dataclass
class NetworkScanResult:
    """Result of a network scan operation."""
    target: str
    port: int
    protocol: str
    service: Optional[str] = None
    version: Optional[str] = None
    status: str = "unknown"  # open, closed, timeout
    response_time_ms: float = 0.0
    scan_time: datetime = None
    error: Optional[str] = None
    
    def __post_init__(self):
        if self.scan_time is None:
            self.scan_time = datetime.now()


class AsyncNetworkScanner:
    """Asynchronous network scanner for concurrent port scanning."""
    
    def __init__(self, max_concurrent: int = 50, timeout: int = 5):
        self.max_concurrent = max_concurrent
        self.timeout = timeout
        self.semaphore = asyncio.Semaphore(max_concurrent)
        self.results: List[NetworkScanResult] = []
        
    async def scan_port(
        self,
        host: str,
        port: int,
        protocol: str = "tcp"
    ) -> NetworkScanResult:
        """Scan a single port asynchronously.
        
        Args:
            host: Target IP or hostname
            port: Port number
            protocol: Protocol (tcp, udp)
            
        Returns:
            NetworkScanResult with scan details
        """
        async with self.semaphore:
            start_time = time.perf_counter()
            result = NetworkScanResult(target=host, port=port, protocol=protocol)
            
            try:
                if protocol == "tcp":
                    result = await self._scan_tcp(host, port, start_time)
                elif protocol == "udp":
                    result = await self._scan_udp(host, port, start_time)
            except asyncio.TimeoutError:
                result.status = "timeout"
                result.error = f"Timeout after {self.timeout}s"
            except Exception as e:
                result.status = "error"
                result.error = str(e)
                
            result.response_time_ms = (time.perf_counter() - start_time) * 1000
            return result
            
    async def _scan_tcp(
        self,
        host: str,
        port: int,
        start_time: float
    ) -> NetworkScanResult:
        """Perform TCP port scan."""
        result = NetworkScanResult(target=host, port=port, protocol="tcp")
        
        try:
            await asyncio.wait_for(
                asyncio.open_connection(host, port),
                timeout=self.timeout
            )
            result.status = "open"
        except asyncio.TimeoutError:
            result.status = "timeout"
        except ConnectionRefusedError:
            result.status = "closed"
        except OSError as e:
            result.status = "closed"
            result.error = str(e)
            
        result.response_time_ms = (time.perf_counter() - start_time) * 1000
        return result
        
    async def _scan_udp(
        self,
        host: str,
        port: int,
        start_time: float
    ) -> NetworkScanResult:
        """Perform UDP port scan."""
        result = NetworkScanResult(target=host, port=port, protocol="udp")
        
        try:
            loop = asyncio.get_event_loop()
            await asyncio.wait_for(
                loop.create_datagram_endpoint(
                    asyncio.DatagramProtocol,
                    remote_addr=(host, port)
                ),
                timeout=self.timeout
            )
            result.status = "open"
        except asyncio.TimeoutError:
            result.status = "filtered"
        except Exception as e:
            result.status = "closed"
            result.error = str(e)
            
        result.response_time_ms = (time.perf_counter() - start_time) * 1000
        return result
        
    async def scan_ports(
        self,
        host: str,
        ports: List[int],
        protocol: str = "tcp"
    ) -> List[NetworkScanResult]:
        """Scan multiple ports concurrently.
        
        Args:
            host: Target host
            ports: List of ports to scan
            protocol: Protocol to use
            
        Returns:
            List of scan results
        """
        tasks = [
            self.scan_port(host, port, protocol)
            for port in ports
        ]
        
        results = await asyncio.gather(*tasks)
        self.results.extend(results)
        return results
        
    async def scan_targets(
        self,
        targets: List[Dict[str, Any]]
    ) -> List[NetworkScanResult]:
        """Scan multiple targets concurrently.
        
        Args:
            targets: List of {'host': ..., 'ports': [...], 'protocol': ...}
            
        Returns:
            List of all scan results
        """
        tasks = [
            self.scan_ports(
                t['host'],
                t.get('ports', [80, 443, 22]),
                t.get('protocol', 'tcp')
            )
            for t in targets
        ]
        
        all_results = await asyncio.gather(*tasks)
        # Flatten list
        flattened = [item for sublist in all_results for item in sublist]
        self.results.extend(flattened)
        return flattened


class AsyncHTTPScanner:
    """Asynchronous HTTP/HTTPS scanner for web services."""
    
    def __init__(self, max_concurrent: int = 20, timeout: int = 10):
        self.max_concurrent = max_concurrent
        self.timeout = timeout
        self.semaphore = asyncio.Semaphore(max_concurrent)
        
    async def scan_url(
        self,
        url: str,
        headers: Optional[Dict] = None
    ) -> Dict[str, Any]:
        """Scan a single URL asynchronously.
        
        Args:
            url: URL to scan
            headers: Optional HTTP headers
            
        Returns:
            Dictionary with response details
        """
        async with self.semaphore:
            start_time = time.perf_counter()
            result = {
                'url': url,
                'status': None,
                'response_time_ms': 0,
                'headers': {},
                'error': None
            }
            
            try:
                timeout = aiohttp.ClientTimeout(total=self.timeout)
                async with aiohttp.ClientSession(timeout=timeout) as session:
                    async with session.get(url, headers=headers) as response:
                        result['status'] = response.status
                        result['headers'] = dict(response.headers)
                        
                        # Check for security headers
                        result['security_headers'] = {
                            'strict-transport-security': 
                                response.headers.get('strict-transport-security'),
                            'x-content-type-options': 
                                response.headers.get('x-content-type-options'),
                            'x-frame-options': 
                                response.headers.get('x-frame-options'),
                            'content-security-policy': 
                                response.headers.get('content-security-policy'),
                        }
            except asyncio.TimeoutError:
                result['error'] = f"Timeout after {self.timeout}s"
            except aiohttp.ClientError as e:
                result['error'] = str(e)
            except Exception as e:
                result['error'] = f"Unexpected error: {str(e)}"
                
            result['response_time_ms'] = (time.perf_counter() - start_time) * 1000
            return result
            
    async def scan_urls(self, urls: List[str]) -> List[Dict[str, Any]]:
        """Scan multiple URLs concurrently.
        
        Args:
            urls: List of URLs to scan
            
        Returns:
            List of scan results
        """
        tasks = [self.scan_url(url) for url in urls]
        return await asyncio.gather(*tasks)


class AsyncDarkwebMonitor:
    """Asynchronous darkweb monitoring operations."""
    
    def __init__(self, max_concurrent: int = 10):
        self.max_concurrent = max_concurrent
        self.semaphore = asyncio.Semaphore(max_concurrent)
        
    async def check_tor_exit_node(self, ip: str) -> Dict[str, Any]:
        """Check if IP is a known Tor exit node.
        
        Args:
            ip: IP address to check
            
        Returns:
            Check result dictionary
        """
        async with self.semaphore:
            try:
                # In production, this would query Tor project's exit node list
                # or a third-party API like stopforumspam.com
                timeout = aiohttp.ClientTimeout(total=10)
                async with aiohttp.ClientSession(timeout=timeout) as session:
                    # Example: Check against tordnsel.torproject.org
                    url = f"https://check.torproject.org/cgi-bin/TorBulkExitList.py?ip={ip}"
                    async with session.get(url) as response:
                        if response.status == 200:
                            text = await response.text()
                            return {
                                'ip': ip,
                                'is_tor_exit': len(text.strip()) > 0,
                                'last_checked': datetime.now().isoformat()
                            }
            except Exception as e:
                logger.error(f"Error checking Tor exit node {ip}: {e}")
                
            return {'ip': ip, 'is_tor_exit': False, 'error': str(e)}
            
    async def check_ips(self, ips: List[str]) -> List[Dict[str, Any]]:
        """Check multiple IPs for Tor exit nodes.
        
        Args:
            ips: List of IP addresses
            
        Returns:
            List of check results
        """
        tasks = [self.check_tor_exit_node(ip) for ip in ips]
        return await asyncio.gather(*tasks)


class AsyncHNDLDetector:
    """Asynchronous HNDL vulnerability detection."""
    
    def __init__(self, max_concurrent: int = 15):
        self.max_concurrent = max_concurrent
        self.semaphore = asyncio.Semaphore(max_concurrent)
        
    async def check_weak_crypto(
        self,
        host: str,
        port: int
    ) -> Dict[str, Any]:
        """Check for weak cryptography on a service.
        
        Args:
            host: Target host
            port: Target port
            
        Returns:
            Cryptography check result
        """
        async with self.semaphore:
            result = {
                'host': host,
                'port': port,
                'weak_algorithms': [],
                'weak_key_sizes': [],
                'error': None
            }
            
            try:
                # This is a simplified example
                # In production, use ssl module or cryptography library
                reader, writer = await asyncio.wait_for(
                    asyncio.open_connection(host, port),
                    timeout=5
                )
                
                # Send TLS Client Hello
                # This is complex and requires proper TLS handling
                writer.close()
                await writer.wait_closed()
                
                result['weak_algorithms'] = []  # Would be populated
                
            except Exception as e:
                result['error'] = str(e)
                
            return result
            
    async def detect_hndl_issues(
        self,
        targets: List[Dict]
    ) -> List[Dict[str, Any]]:
        """Detect HNDL issues across multiple targets.
        
        Args:
            targets: List of targets to check
            
        Returns:
            List of detection results
        """
        tasks = [
            self.check_weak_crypto(t['host'], t['port'])
            for t in targets
        ]
        return await asyncio.gather(*tasks)


# Convenient async runner functions

async def run_async_network_scan(
    targets: List[Dict[str, Any]],
    max_concurrent: int = 50
) -> List[NetworkScanResult]:
    """Run async network scan on multiple targets.
    
    Args:
        targets: List of targets with host/ports/protocol
        max_concurrent: Maximum concurrent scans
        
    Returns:
        List of scan results
    """
    scanner = AsyncNetworkScanner(max_concurrent=max_concurrent)
    return await scanner.scan_targets(targets)


async def run_async_http_scan(
    urls: List[str],
    max_concurrent: int = 20
) -> List[Dict[str, Any]]:
    """Run async HTTP scans on multiple URLs.
    
    Args:
        urls: List of URLs to scan
        max_concurrent: Maximum concurrent requests
        
    Returns:
        List of scan results
    """
    scanner = AsyncHTTPScanner(max_concurrent=max_concurrent)
    return await scanner.scan_urls(urls)


def run_async_scan(
    scan_func: Callable[..., Coroutine]
) -> Any:
    """Run async function in event loop.
    
    Args:
        scan_func: Async function to run
        
    Returns:
        Result of async function
    """
    try:
        loop = asyncio.get_event_loop()
        if loop.is_closed():
            loop = asyncio.new_event_loop()
            asyncio.set_event_loop(loop)
    except RuntimeError:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        
    try:
        return loop.run_until_complete(scan_func())
    finally:
        # Don't close loop if it was already running
        if not loop.is_running():
            loop.close()


class AsyncScanOrchestrator:
    """Orchestrate multiple async scans efficiently."""
    
    def __init__(self):
        self.network_scanner = AsyncNetworkScanner()
        self.http_scanner = AsyncHTTPScanner()
        self.darkweb_monitor = AsyncDarkwebMonitor()
        self.hndl_detector = AsyncHNDLDetector()
        
    async def run_comprehensive_scan(
        self,
        targets: List[Dict[str, Any]]
    ) -> Dict[str, Any]:
        """Run comprehensive async scan across all modules.
        
        Args:
            targets: List of targets
            
        Returns:
            Combined results from all scanners
        """
        tasks = [
            self.network_scanner.scan_targets(targets),
            self.http_scanner.scan_urls(
                [f"http://{t['host']}:{p}" for t in targets for p in t.get('ports', [])]
            ),
            self.darkweb_monitor.check_ips([t['host'] for t in targets]),
            self.hndl_detector.detect_hndl_issues(targets)
        ]
        
        network_results, http_results, darkweb_results, hndl_results = await asyncio.gather(*tasks)
        
        return {
            'network_scans': network_results,
            'http_scans': http_results,
            'darkweb_checks': darkweb_results,
            'hndl_detections': hndl_results,
            'timestamp': datetime.now().isoformat()
        }
