"""Core scanning engine with async support."""
import asyncio
import time
from typing import Optional, List
from urllib.parse import urlparse

import aiohttp
import dns.resolver
from dns.exception import DNSException

from reclaimor.config import Config
from reclaimor.scanner.result import ScanResult
from reclaimor.fingerprints.loader import FingerprintLoader
from reclaimor.scanner.dns_checker import DNSChecker
from reclaimor.scanner.http_checker import HTTPChecker
from reclaimor.scanner.tls_checker import TLSChecker
from reclaimor.exploit.exploiter import Exploiter
from reclaimor.utils.logger import setup_logger

logger = setup_logger(__name__)


class Scanner:
    """Main scanning engine for subdomain takeover detection."""
    
    def __init__(self, config: Config):
        self.config = config
        self.fingerprints = FingerprintLoader(config.fingerprints_dir).load()
        self.dns_checker = DNSChecker(config)
        self.http_checker = HTTPChecker(config)
        self.tls_checker = TLSChecker(config)
        self.exploiter = Exploiter(config) if config.auto_exploit else None
        self.session: Optional[aiohttp.ClientSession] = None
        self._rate_limiter = asyncio.Semaphore(config.rate_limit)
    
    async def __aenter__(self):
        """Async context manager entry."""
        self.session = aiohttp.ClientSession(
            timeout=aiohttp.ClientTimeout(total=self.config.timeout),
            headers={"User-Agent": self.config.user_agent},
        )
        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 scan(self, domain: str) -> ScanResult:
        """
        Scan a single domain for subdomain takeover vulnerability.
        
        Args:
            domain: Domain or URL to scan
            
        Returns:
            ScanResult with vulnerability details
        """
        start_time = time.time()
        domain = self._normalize_domain(domain)
        
        logger.info(f"Scanning {domain}")
        
        result = ScanResult(
            domain=domain,
            vulnerable=False,
            confidence=0,
        )
        
        try:
            # Rate limiting
            async with self._rate_limiter:
                # Step 1: DNS Analysis
                dns_info = await self.dns_checker.check(domain)
                result.dns_records = dns_info.get("records", {})
                result.cname = dns_info.get("cname")
                
                if not dns_info.get("resolvable"):
                    result.notes.append("Domain does not resolve")
                    result.scan_duration = time.time() - start_time
                    return result
                
                # Step 2: HTTP Analysis
                if not self.session:
                    self.session = aiohttp.ClientSession(
                        timeout=aiohttp.ClientTimeout(total=self.config.timeout),
                        headers={"User-Agent": self.config.user_agent},
                    )
                
                http_info = await self.http_checker.check(domain, self.session)
                result.status_code = http_info.get("status_code")
                result.response_body = http_info.get("body", "")[:1000]  # Limit body size
                
                # Step 3: TLS Analysis
                tls_info = await self.tls_checker.check(domain)
                result.tls_info = tls_info
                
                # Step 4: Fingerprint Matching
                match_result = self._match_fingerprints(dns_info, http_info, tls_info)
                
                if match_result:
                    result.vulnerable = True
                    result.confidence = match_result["confidence"]
                    result.provider = match_result["provider"]
                    result.fingerprint_matched = match_result["fingerprint"]
                    result.notes.extend(match_result.get("notes", []))
                    
                    # Step 5: Auto-Exploitation (if enabled)
                    if self.config.auto_exploit and self.exploiter:
                        exploit_result = await self.exploiter.attempt_takeover(
                            domain, result.provider, dns_info, http_info
                        )
                        result.exploit_attempted = True
                        result.exploit_successful = exploit_result.get("success", False)
                        result.exploit_details = exploit_result
                        
                        if result.exploit_successful:
                            result.confidence = min(100, result.confidence + 10)
                            result.notes.append("Exploitation successful - CONFIRMED VULNERABLE")
        
        except Exception as e:
            logger.error(f"Error scanning {domain}: {e}")
            result.error = str(e)
        
        result.scan_duration = time.time() - start_time
        return result
    
    def _normalize_domain(self, domain: str) -> str:
        """Normalize domain/URL to just the hostname."""
        if "://" in domain:
            parsed = urlparse(domain)
            return parsed.netloc or parsed.path
        return domain.strip()
    
    def _match_fingerprints(self, dns_info, http_info, tls_info) -> Optional[dict]:
        """
        Match response against known fingerprints.
        
        Returns dict with provider, confidence, fingerprint, and notes if match found.
        """
        best_match = None
        highest_confidence = 0
        
        for fingerprint in self.fingerprints:
            confidence = self._calculate_match_confidence(
                fingerprint, dns_info, http_info, tls_info
            )
            
            if confidence > highest_confidence:
                highest_confidence = confidence
                best_match = {
                    "provider": fingerprint["provider"],
                    "confidence": confidence,
                    "fingerprint": fingerprint.get("name", "unknown"),
                    "notes": [fingerprint.get("notes", "")],
                }
        
        # Only return if confidence threshold met
        if highest_confidence >= 50:
            return best_match
        
        return None
    
    def _calculate_match_confidence(self, fingerprint, dns_info, http_info, tls_info) -> int:
        """Calculate confidence score for a fingerprint match."""
        confidence = 0
        matches = []
        
        # DNS-based checks
        cname = dns_info.get("cname", "").lower()
        if "cname_pattern" in fingerprint:
            if fingerprint["cname_pattern"].lower() in cname:
                confidence += 30
                matches.append("CNAME match")
        
        # HTTP status code check
        if "status_code" in fingerprint:
            if http_info.get("status_code") == fingerprint["status_code"]:
                confidence += 20
                matches.append("Status code match")
        
        # Body pattern matching
        body = http_info.get("body", "").lower()
        if "body_pattern" in fingerprint:
            pattern = fingerprint["body_pattern"].lower()
            if pattern in body:
                confidence += 40
                matches.append("Body pattern match")
        
        # Header checks
        headers = http_info.get("headers", {})
        if "header_pattern" in fingerprint:
            header_name = fingerprint["header_pattern"].get("name", "").lower()
            header_value = fingerprint["header_pattern"].get("value", "").lower()
            if header_name in {k.lower(): v for k, v in headers.items()}:
                if header_value in headers.get(header_name, "").lower():
                    confidence += 25
                    matches.append("Header match")
        
        # TLS certificate mismatch (indicates potential takeover)
        if tls_info and tls_info.get("mismatch"):
            confidence += 15
            matches.append("TLS mismatch")
        
        # Apply fingerprint weight
        weight = fingerprint.get("confidence_weight", 1.0)
        confidence = int(confidence * weight)
        
        return min(100, confidence)
    
    async def scan_batch(self, domains: List[str]) -> List[ScanResult]:
        """Scan multiple domains concurrently."""
        async with self:
            tasks = [self.scan(domain) for domain in domains]
            return await asyncio.gather(*tasks)
