"""
Tests for CHRONOS Network Detection Module
"""

import pytest
from datetime import datetime, timedelta
from unittest.mock import MagicMock, patch

from chronos.core.detect import (
    BPFFilter,
    TLSDetector,
    TrafficAnalyzer,
    NetworkMonitor,
    PacketCapture,
    PacketInfo,
    ConnectionInfo,
    TLSHandshakeInfo,
    TrafficMetadata,
    NetworkAlert,
    Protocol,
    TLSVersion,
    AlertSeverity,
    AlertType,
)


class TestBPFFilter:
    """Tests for BPF filter builder."""
    
    def test_empty_filter(self):
        """Test empty filter builds to empty string."""
        f = BPFFilter()
        assert f.build() == ""
    
    def test_single_port(self):
        """Test single port filter."""
        f = BPFFilter().port(443)
        assert f.build() == "(port 443)"
    
    def test_tcp_protocol(self):
        """Test TCP protocol filter."""
        f = BPFFilter().tcp()
        assert f.build() == "(tcp)"
    
    def test_combined_filter(self):
        """Test combined port and protocol filter."""
        f = BPFFilter().tcp().port(443)
        assert f.build() == "(tcp) and (port 443)"
    
    def test_host_filter(self):
        """Test host filter."""
        f = BPFFilter().host("192.168.1.1")
        assert f.build() == "(host 192.168.1.1)"
    
    def test_network_filter(self):
        """Test network CIDR filter."""
        f = BPFFilter().src_net("10.0.0.0/8")
        assert f.build() == "(src net 10.0.0.0/8)"
    
    def test_exclude_port(self):
        """Test port exclusion."""
        f = BPFFilter().exclude_port(22)
        assert f.build() == "(not port 22)"
    
    def test_tcp_flags(self):
        """Test TCP flags filter."""
        f = BPFFilter().tcp_flags("syn")
        assert "tcp-syn" in f.build()
    
    def test_or_operator(self):
        """Test OR operator between filters."""
        f = BPFFilter().port(80).port(443)
        result = f.build(operator="or")
        assert "or" in result
    
    def test_common_filters(self):
        """Test common pre-built filters."""
        common = BPFFilter.common_filters()
        assert "tls" in common
        assert "http" in common
        assert "dns" in common
        assert "443" in common["tls"]
    
    def test_from_string(self):
        """Test creating filter from raw string."""
        f = BPFFilter.from_string("tcp and port 80")
        assert "tcp and port 80" in f.build()
    
    def test_chaining(self):
        """Test method chaining returns self."""
        f = BPFFilter()
        result = f.tcp().port(443).host("1.1.1.1")
        assert result is f


class TestPacketInfo:
    """Tests for PacketInfo dataclass."""
    
    def test_create_packet_info(self):
        """Test creating PacketInfo instance."""
        packet = PacketInfo(
            timestamp=datetime.now(),
            src_ip="192.168.1.100",
            dst_ip="8.8.8.8",
            src_port=12345,
            dst_port=443,
            protocol=Protocol.TCP,
            length=1500,
            payload_length=1400,
        )
        assert packet.src_ip == "192.168.1.100"
        assert packet.is_tcp
        assert not packet.is_udp
    
    def test_connection_tuple(self):
        """Test connection tuple generation."""
        packet = PacketInfo(
            timestamp=datetime.now(),
            src_ip="10.0.0.1",
            dst_ip="10.0.0.2",
            src_port=1234,
            dst_port=80,
            protocol=Protocol.TCP,
            length=100,
            payload_length=50,
        )
        assert packet.connection_tuple == ("10.0.0.1", 1234, "10.0.0.2", 80)
    
    def test_to_dict(self):
        """Test conversion to dictionary."""
        packet = PacketInfo(
            timestamp=datetime.now(),
            src_ip="192.168.1.1",
            dst_ip="192.168.1.2",
            src_port=80,
            dst_port=8080,
            protocol=Protocol.TCP,
            length=100,
            payload_length=50,
        )
        d = packet.to_dict()
        assert "src_ip" in d
        assert d["protocol"] == "tcp"


class TestConnectionInfo:
    """Tests for ConnectionInfo dataclass."""
    
    def test_connection_id(self):
        """Test connection ID generation."""
        conn = ConnectionInfo(
            src_ip="192.168.1.1",
            src_port=12345,
            dst_ip="10.0.0.1",
            dst_port=443,
            protocol=Protocol.TCP,
        )
        assert "192.168.1.1:12345" in conn.connection_id
        assert "10.0.0.1:443" in conn.connection_id
    
    def test_update_stats(self):
        """Test updating connection stats."""
        conn = ConnectionInfo(
            src_ip="192.168.1.1",
            src_port=12345,
            dst_ip="10.0.0.1",
            dst_port=443,
            protocol=Protocol.TCP,
        )
        packet = PacketInfo(
            timestamp=datetime.now(),
            src_ip="192.168.1.1",
            dst_ip="10.0.0.1",
            src_port=12345,
            dst_port=443,
            protocol=Protocol.TCP,
            length=1000,
            payload_length=900,
        )
        conn.update(packet, is_outbound=True)
        assert conn.packets_sent == 1
        assert conn.bytes_sent == 1000
    
    def test_duration(self):
        """Test connection duration calculation."""
        start = datetime.now()
        conn = ConnectionInfo(
            src_ip="192.168.1.1",
            src_port=12345,
            dst_ip="10.0.0.1",
            dst_port=443,
            protocol=Protocol.TCP,
            first_seen=start,
            last_seen=start + timedelta(seconds=30),
        )
        assert conn.duration == 30.0


class TestTLSHandshakeInfo:
    """Tests for TLSHandshakeInfo dataclass."""
    
    def test_deprecated_version(self):
        """Test deprecated version detection."""
        info = TLSHandshakeInfo(version=TLSVersion.TLS_1_0)
        assert info.is_deprecated
        
        info2 = TLSHandshakeInfo(version=TLSVersion.TLS_1_3)
        assert not info2.is_deprecated
    
    def test_security_score(self):
        """Test security score calculation."""
        # TLS 1.3 should score high
        info = TLSHandshakeInfo(version=TLSVersion.TLS_1_3)
        assert info.security_score >= 70
        
        # SSL 3.0 should score very low
        info2 = TLSHandshakeInfo(version=TLSVersion.SSL_3_0)
        assert info2.security_score < 30
    
    def test_quantum_safe_bonus(self):
        """Test quantum-safe bonus in scoring."""
        info = TLSHandshakeInfo(
            version=TLSVersion.TLS_1_3,
            is_quantum_safe=True,
        )
        base_info = TLSHandshakeInfo(version=TLSVersion.TLS_1_3)
        assert info.security_score > base_info.security_score


class TestTLSDetector:
    """Tests for TLS detection."""
    
    def test_is_tls_packet(self):
        """Test TLS packet detection."""
        detector = TLSDetector()
        
        # Valid TLS record header
        tls_data = bytes([22, 0x03, 0x01, 0x00, 0x10])  # Handshake, TLS 1.0
        assert detector.is_tls_packet(tls_data)
        
        # Non-TLS data
        http_data = b"GET / HTTP/1.1\r\n"
        assert not detector.is_tls_packet(http_data)
        
        # Too short
        assert not detector.is_tls_packet(b"\x16\x03")
    
    def test_detect_handshake(self):
        """Test handshake detection."""
        detector = TLSDetector()
        
        # Minimal Client Hello
        client_hello = bytes([
            22,        # Handshake
            0x03, 0x03,  # TLS 1.2
            0x00, 0x10,  # Length
            1,         # Client Hello
            0, 0, 12,  # Handshake length
            0x03, 0x03,  # Version TLS 1.2
        ]) + b'\x00' * 32 + bytes([0])
        
        info = detector.detect_handshake(client_hello, "test-conn")
        assert info is not None
        assert info.version == TLSVersion.TLS_1_2


class TestTrafficAnalyzer:
    """Tests for traffic analyzer."""
    
    def test_process_packet(self):
        """Test packet processing."""
        analyzer = TrafficAnalyzer("eth0")
        
        packet = PacketInfo(
            timestamp=datetime.now(),
            src_ip="192.168.1.1",
            dst_ip="8.8.8.8",
            src_port=12345,
            dst_port=443,
            protocol=Protocol.TCP,
            length=1000,
            payload_length=900,
        )
        
        analyzer.process_packet(packet)
        
        assert analyzer.metadata.total_packets == 1
        assert analyzer.metadata.total_bytes == 1000
        assert analyzer.metadata.tcp_packets == 1
    
    def test_dns_detection(self):
        """Test DNS traffic detection."""
        analyzer = TrafficAnalyzer("eth0")
        
        packet = PacketInfo(
            timestamp=datetime.now(),
            src_ip="192.168.1.1",
            dst_ip="8.8.8.8",
            src_port=12345,
            dst_port=53,
            protocol=Protocol.UDP,
            length=100,
            payload_length=80,
        )
        
        result = analyzer.process_packet(packet)
        
        assert analyzer.metadata.dns_queries == 1
        assert result.metadata.get("is_dns") is True
    
    def test_connection_tracking(self):
        """Test connection tracking."""
        analyzer = TrafficAnalyzer("eth0")
        
        # Same connection, two packets
        for _ in range(2):
            packet = PacketInfo(
                timestamp=datetime.now(),
                src_ip="192.168.1.1",
                dst_ip="10.0.0.1",
                src_port=12345,
                dst_port=80,
                protocol=Protocol.TCP,
                length=500,
                payload_length=400,
            )
            analyzer.process_packet(packet)
        
        connections = analyzer.get_connections()
        assert len(connections) == 1
        assert connections[0].total_packets == 2


class TestTrafficMetadata:
    """Tests for traffic metadata."""
    
    def test_packets_per_second(self):
        """Test PPS calculation."""
        start = datetime.now()
        metadata = TrafficMetadata(
            interface="eth0",
            start_time=start,
            end_time=start + timedelta(seconds=10),
            total_packets=100,
        )
        assert metadata.packets_per_second == 10.0
    
    def test_update_from_packet(self):
        """Test metadata update from packet."""
        metadata = TrafficMetadata(
            interface="eth0",
            start_time=datetime.now(),
        )
        
        packet = PacketInfo(
            timestamp=datetime.now(),
            src_ip="192.168.1.1",
            dst_ip="8.8.8.8",
            src_port=12345,
            dst_port=443,
            protocol=Protocol.TCP,
            length=1500,
            payload_length=1400,
        )
        
        metadata.update_from_packet(packet)
        
        assert metadata.total_packets == 1
        assert metadata.tcp_packets == 1
        assert "192.168.1.1" in metadata.unique_src_ips
        assert 443 in metadata.port_distribution


class TestNetworkAlert:
    """Tests for network alerts."""
    
    def test_create_alert(self):
        """Test alert creation."""
        alert = NetworkAlert(
            id="test-alert-1",
            alert_type=AlertType.WEAK_TLS,
            severity=AlertSeverity.HIGH,
            message="Weak TLS version detected",
        )
        assert alert.severity == AlertSeverity.HIGH
        assert not alert.acknowledged
    
    def test_alert_to_dict(self):
        """Test alert serialization."""
        alert = NetworkAlert(
            id="test-alert-2",
            alert_type=AlertType.PORT_SCAN,
            severity=AlertSeverity.MEDIUM,
            message="Potential port scan detected",
            src_ip="192.168.1.100",
        )
        d = alert.to_dict()
        assert d["alert_type"] == "port_scan"
        assert d["src_ip"] == "192.168.1.100"


class TestNetworkMonitor:
    """Tests for NetworkMonitor class."""
    
    def test_create_monitor(self):
        """Test monitor creation."""
        monitor = NetworkMonitor()
        assert not monitor.is_running
    
    def test_set_filter(self):
        """Test filter setting."""
        monitor = NetworkMonitor()
        result = monitor.set_filter(BPFFilter().tcp().port(443))
        assert result is monitor  # Test chaining
    
    def test_set_interface(self):
        """Test interface setting."""
        monitor = NetworkMonitor()
        monitor.set_interface("eth0")
        assert monitor.interface == "eth0"
    
    def test_callback_registration(self):
        """Test callback registration."""
        monitor = NetworkMonitor()
        callback = MagicMock()
        result = monitor.on_packet(callback)
        assert result is monitor
        assert callback in monitor._packet_callbacks


class TestPacketCapture:
    """Tests for PacketCapture class."""
    
    def test_list_interfaces(self):
        """Test interface listing."""
        interfaces = PacketCapture.list_interfaces()
        assert isinstance(interfaces, list)
        # Should have at least loopback
        assert len(interfaces) > 0
    
    def test_capture_not_running(self):
        """Test capture state before start."""
        capture = PacketCapture()
        assert not capture.is_running


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
