"""
Ethernet Data decoders for Chapter 10 data types
Supports Ethernet Data Formats 0-1 (0x68-0x69)
"""

import struct
from typing import Dict, Any, Optional
from .base import DataTypeDecoder, DecodedPayload


class EthernetDecoder(DataTypeDecoder):
    """Decoder for Ethernet Data types (0x68-0x69)"""
    
    def __init__(self):
        super().__init__()
        self.data_type_base = 0x68
        self.data_type_name = "Ethernet Data"
        self.supported_formats = [0x68, 0x69]
    
    def can_decode(self, data_type: int) -> bool:
        return data_type in [0x68, 0x69]
    
    def get_data_type_name(self, data_type: int) -> str:
        format_names = {
            0x68: "Ethernet Data Format 0",
            0x69: "Ethernet UDP Payload"
        }
        return format_names.get(data_type, f"Ethernet Format {data_type & 0x0F}")
    
    def decode(self, payload: bytes, ch10_header: Dict[str, Any]) -> Optional[DecodedPayload]:
        """Decode Ethernet payload"""
        data_type = ch10_header.get('data_type', 0)
        
        if not self.can_decode(data_type):
            return None
        
        if data_type == 0x68:
            return self._decode_ethernet_format0(payload, ch10_header)
        elif data_type == 0x69:
            return self._decode_ethernet_udp_payload(payload, ch10_header)
        
        return None
    
    def _decode_ethernet_format0(self, payload: bytes, ch10_header: Dict[str, Any]) -> DecodedPayload:
        """Decode Ethernet Format 0 (Full Ethernet Frame)"""
        decoded_data = {}
        errors = []
        
        # Parse IPH
        iph = self._parse_intra_packet_header(payload)
        if iph:
            decoded_data.update(iph)
            data_start = iph['data_start']
        else:
            data_start = 0
            errors.append("Failed to parse intra-packet header")
        
        # Parse Ethernet Format 0 header
        if data_start + 12 <= len(payload):
            eth_header = self._safe_unpack('<III', payload, data_start)
            if eth_header:
                frame_status = eth_header[2]
                
                decoded_data.update({
                    'ethernet_timestamp': eth_header[1],
                    'frame_status_word': frame_status,
                    'frame_length': frame_status & 0x3FFF,
                    'length_error': bool(frame_status & 0x8000),
                    'crc_error': bool(frame_status & 0x10000),
                    'content_type': (frame_status >> 28) & 0x3
                })
                
                # Decode content type
                content_types = {
                    0: "Full MAC frame",
                    1: "Payload only", 
                    2: "Reserved",
                    3: "Reserved"
                }
                decoded_data['content_type_description'] = content_types.get(
                    decoded_data['content_type'], "Unknown"
                )
                
                # Extract Ethernet frame data
                frame_data_start = data_start + 12
                frame_length = decoded_data['frame_length']
                
                if frame_data_start + frame_length <= len(payload):
                    frame_data = payload[frame_data_start:frame_data_start + frame_length]
                    
                    # Parse Ethernet header if full MAC frame
                    if decoded_data['content_type'] == 0 and len(frame_data) >= 14:
                        eth_parsed = self._parse_ethernet_header(frame_data)
                        decoded_data.update(eth_parsed)
                    else:
                        decoded_data['raw_frame_data'] = frame_data[:64].hex()  # First 64 bytes
                        
                    decoded_data['actual_frame_length'] = len(frame_data)
                else:
                    errors.append("Frame data extends beyond payload")
            else:
                errors.append("Failed to parse Ethernet Format 0 header")
        else:
            errors.append("Insufficient data for Ethernet header")
        
        return DecodedPayload(
            data_type=0x68,
            data_type_name="Ethernet Data Format 0",
            format_version=0,
            decoded_data=decoded_data,
            raw_payload=payload,
            errors=errors,
            metadata={'decoder': 'EthernetDecoder'}
        )
    
    def _decode_ethernet_udp_payload(self, payload: bytes, ch10_header: Dict[str, Any]) -> DecodedPayload:
        """Decode Ethernet UDP Payload (Format 1)"""
        decoded_data = {}
        errors = []
        
        # Parse IPH
        iph = self._parse_intra_packet_header(payload)
        if iph:
            decoded_data.update(iph)
            data_start = iph['data_start']
        else:
            data_start = 0
            errors.append("Failed to parse intra-packet header")
        
        # Parse UDP payload header
        if data_start + 16 <= len(payload):
            udp_header = self._safe_unpack('<IIHHHH', payload, data_start)
            if udp_header:
                decoded_data.update({
                    'udp_timestamp': udp_header[1],
                    'src_ip': self._ip_to_string(udp_header[2]),
                    'dst_ip': self._ip_to_string(udp_header[3]), 
                    'src_port': udp_header[4],
                    'dst_port': udp_header[5]
                })
                
                # Extract UDP payload
                udp_payload_start = data_start + 16
                if udp_payload_start < len(payload):
                    udp_payload = payload[udp_payload_start:]
                    decoded_data['udp_payload_length'] = len(udp_payload)
                    decoded_data['udp_payload_preview'] = udp_payload[:64].hex()
                    
                    # Try to identify payload content
                    payload_info = self._analyze_udp_payload(udp_payload)
                    decoded_data.update(payload_info)
            else:
                errors.append("Failed to parse UDP header")
        else:
            errors.append("Insufficient data for UDP header")
        
        return DecodedPayload(
            data_type=0x69,
            data_type_name="Ethernet UDP Payload",
            format_version=1,
            decoded_data=decoded_data,
            raw_payload=payload,
            errors=errors,
            metadata={'decoder': 'EthernetDecoder'}
        )
    
    def _parse_ethernet_header(self, frame_data: bytes) -> Dict[str, Any]:
        """Parse Ethernet MAC header"""
        if len(frame_data) < 14:
            return {'eth_parse_error': 'Insufficient data for Ethernet header'}
        
        # Parse MAC addresses and EtherType
        dst_mac = frame_data[0:6]
        src_mac = frame_data[6:12] 
        ethertype = struct.unpack('>H', frame_data[12:14])[0]
        
        eth_data = {
            'dst_mac': ':'.join(f'{b:02x}' for b in dst_mac),
            'src_mac': ':'.join(f'{b:02x}' for b in src_mac),
            'ethertype': f'0x{ethertype:04x}',
            'ethertype_description': self._decode_ethertype(ethertype)
        }
        
        # Parse payload based on EtherType
        if ethertype == 0x0800 and len(frame_data) >= 34:  # IPv4
            ip_data = self._parse_ip_header(frame_data[14:])
            eth_data.update(ip_data)
        elif ethertype == 0x0806 and len(frame_data) >= 42:  # ARP
            arp_data = self._parse_arp_header(frame_data[14:])
            eth_data.update(arp_data)
        
        return eth_data
    
    def _parse_ip_header(self, ip_data: bytes) -> Dict[str, Any]:
        """Parse IPv4 header"""
        if len(ip_data) < 20:
            return {'ip_parse_error': 'Insufficient data for IP header'}
        
        version_ihl = ip_data[0]
        version = (version_ihl >> 4) & 0x0F
        ihl = version_ihl & 0x0F
        
        if version != 4:
            return {'ip_parse_error': f'Unsupported IP version: {version}'}
        
        tos, total_length, identification, flags_fragment = struct.unpack('>BHHH', ip_data[1:9])
        ttl, protocol, checksum = struct.unpack('>BBH', ip_data[8:12])
        src_ip = struct.unpack('>I', ip_data[12:16])[0]
        dst_ip = struct.unpack('>I', ip_data[16:20])[0]
        
        return {
            'ip_version': version,
            'ip_header_length': ihl * 4,
            'ip_tos': tos,
            'ip_total_length': total_length,
            'ip_id': identification,
            'ip_ttl': ttl,
            'ip_protocol': protocol,
            'ip_src': self._ip_to_string(src_ip),
            'ip_dst': self._ip_to_string(dst_ip),
            'ip_protocol_name': self._decode_ip_protocol(protocol)
        }
    
    def _parse_arp_header(self, arp_data: bytes) -> Dict[str, Any]:
        """Parse ARP header"""
        if len(arp_data) < 28:
            return {'arp_parse_error': 'Insufficient data for ARP header'}
        
        hw_type, proto_type, hw_len, proto_len, opcode = struct.unpack('>HHBBH', arp_data[0:8])
        sender_hw = arp_data[8:14]
        sender_proto = struct.unpack('>I', arp_data[14:18])[0]
        target_hw = arp_data[18:24]
        target_proto = struct.unpack('>I', arp_data[24:28])[0]
        
        return {
            'arp_hw_type': hw_type,
            'arp_proto_type': f'0x{proto_type:04x}',
            'arp_opcode': opcode,
            'arp_opcode_description': 'Request' if opcode == 1 else 'Reply' if opcode == 2 else f'Unknown ({opcode})',
            'arp_sender_hw': ':'.join(f'{b:02x}' for b in sender_hw),
            'arp_sender_ip': self._ip_to_string(sender_proto),
            'arp_target_hw': ':'.join(f'{b:02x}' for b in target_hw),
            'arp_target_ip': self._ip_to_string(target_proto)
        }
    
    def _analyze_udp_payload(self, payload: bytes) -> Dict[str, Any]:
        """Analyze UDP payload content"""
        analysis = {}
        
        if len(payload) == 0:
            return {'payload_analysis': 'Empty payload'}
        
        # Check for common protocols
        if len(payload) >= 4:
            # Check for DNS (port 53 patterns)
            if payload[0:2] in [b'\x00\x01', b'\x81\x80', b'\x01\x00']:
                analysis['possible_protocol'] = 'DNS'
            # Check for DHCP magic cookie
            elif payload[:4] == b'\x63\x82\x53\x63':
                analysis['possible_protocol'] = 'DHCP'
            # Check for RTP (version 2)
            elif (payload[0] & 0xC0) == 0x80:
                analysis['possible_protocol'] = 'RTP'
            else:
                analysis['possible_protocol'] = 'Unknown'
        
        # Basic statistics
        analysis['payload_entropy'] = self._calculate_entropy(payload[:256])  # First 256 bytes
        analysis['null_bytes'] = payload.count(0)
        analysis['printable_chars'] = sum(1 for b in payload[:256] if 32 <= b <= 126)
        
        return analysis
    
    def _calculate_entropy(self, data: bytes) -> float:
        """Calculate Shannon entropy of data"""
        if not data:
            return 0.0
        
        counts = [0] * 256
        for byte in data:
            counts[byte] += 1
        
        entropy = 0.0
        length = len(data)
        for count in counts:
            if count > 0:
                p = count / length
                entropy -= p * (p.bit_length() - 1)  # log2(p)
        
        return entropy
    
    def _ip_to_string(self, ip_int: int) -> str:
        """Convert 32-bit integer to IP address string"""
        return f"{(ip_int >> 24) & 0xFF}.{(ip_int >> 16) & 0xFF}.{(ip_int >> 8) & 0xFF}.{ip_int & 0xFF}"
    
    def _decode_ethertype(self, ethertype: int) -> str:
        """Decode EtherType field"""
        types = {
            0x0800: "IPv4",
            0x0806: "ARP", 
            0x86DD: "IPv6",
            0x8100: "VLAN",
            0x88F7: "PTP"
        }
        return types.get(ethertype, f"Unknown (0x{ethertype:04x})")
    
    def _decode_ip_protocol(self, protocol: int) -> str:
        """Decode IP protocol field"""
        protocols = {
            1: "ICMP",
            6: "TCP",
            17: "UDP",
            89: "OSPF",
            132: "SCTP"
        }
        return protocols.get(protocol, f"Unknown ({protocol})")