"""
Chapter 10 (IRIG106) protocol dissector and packet handling
"""

import struct
from typing import Dict, List, Optional, Any, Tuple
from dataclasses import dataclass, field
from abc import ABC, abstractmethod

try:
    from scapy.all import Packet, Raw, IP, UDP
except ImportError:
    print("Error: scapy library required. Install with: pip install scapy")
    import sys
    sys.exit(1)

try:
    import numpy as np
except ImportError:
    print("Error: numpy library required. Install with: pip install numpy")
    import sys
    sys.exit(1)

from .base import ProtocolDissector, DissectionResult, ProtocolType
from .decoders.registry import decoder_registry


class Chapter10Dissector(ProtocolDissector):
    """Chapter 10 packet dissector based on IRIG 106-17 specification"""
    
    # Channel data types from Chapter 10 spec
    CH10_DATA_TYPES = {
        0x08: "PCM Format 1",
        0x09: "Time Format 1", 
        0x11: "1553 Format 1",
        0x19: "Image Format 0",
        0x21: "UART Format 0",
        0x30: "1394 Format 1",
        0x38: "Parallel Format 1",
        0x40: "Ethernet Format 0",
        0x48: "TSPI/CTS Format 1",
        0x50: "Controller Area Network Bus",
        0x58: "Fibre Channel Format 1",
        0x60: "IRIG 106 Format 1",
        0x68: "Video Format 0",
        0x69: "Video Format 1", 
        0x6A: "Video Format 2",
        0x70: "Message Format 0",
        0x78: "ARINC 429 Format 0",
        0x04: "PCM Format 0",
        0x72: "Analog Format 2",
        0x73: "Analog Format 3", 
        0x74: "Analog Format 4",
        0x75: "Analog Format 5",
        0x76: "Analog Format 6",
        0x77: "Analog Format 7",
        0x78: "Analog Format 8",
        0xB4: "User Defined Format"
    }
    
    def __init__(self):
        self.sync_pattern = 0xEB25  # Chapter 10 sync pattern
    
    def can_dissect(self, packet: Packet) -> bool:
        """Check if packet contains Chapter 10 data"""
        if not packet.haslayer(Raw):
            return False
            
        raw_data = bytes(packet[Raw])
        if len(raw_data) < 24:  # Minimum Ch10 header size
            return False
            
        return self._find_chapter10_offset(raw_data) is not None
    
    def get_protocol_type(self) -> ProtocolType:
        return ProtocolType.CHAPTER10
    
    def dissect(self, packet: Packet) -> Optional[DissectionResult]:
        """Dissect Chapter 10 packet (handles embedded formats)"""
        if not packet.haslayer(Raw):
            return None
            
        raw_data = bytes(packet[Raw])
        if len(raw_data) < 24:  # Minimum Ch10 header size
            return None
            
        # Search for Chapter 10 sync pattern in the payload
        ch10_offset = self._find_chapter10_offset(raw_data)
        
        if ch10_offset is None:
            return None
            
        try:
            # Parse Chapter 10 header starting at the found offset
            if ch10_offset + 24 > len(raw_data):
                return None
                
            header_data = raw_data[ch10_offset:ch10_offset + 24]
            header = self._parse_header(header_data)
            
            if header.get('sync_pattern') != self.sync_pattern:
                return None
                
            result = DissectionResult(
                protocol=ProtocolType.CHAPTER10,
                fields=header
            )
            
            # Add container information
            if ch10_offset > 0:
                result.fields['container_offset'] = ch10_offset
                result.fields['container_header'] = raw_data[:ch10_offset].hex()
            
            # Extract payload if present
            packet_length = header.get('packet_length', 0)
            payload_start = ch10_offset + 24
            
            if packet_length > 24 and payload_start + (packet_length - 24) <= len(raw_data):
                result.payload = raw_data[payload_start:payload_start + (packet_length - 24)]
                
                # Use new decoder framework for payload parsing
                decoded_payload = decoder_registry.decode_payload(result.payload, header)
                if decoded_payload:
                    result.fields['decoded_payload'] = {
                        'data_type_name': decoded_payload.data_type_name,
                        'format_version': decoded_payload.format_version,
                        'decoded_data': decoded_payload.decoded_data,
                        'decoder_errors': decoded_payload.errors,
                        'decoder_metadata': decoded_payload.metadata
                    }
                
                # Legacy Ethernet Format 0 parsing (for backwards compatibility)
                data_type = header.get('data_type', 0)
                if data_type == 0x40:  # Ethernet Format 0
                    eth_data = self._parse_ethernet_fmt0(result.payload)
                    if eth_data:
                        result.fields.update(eth_data)
            
            return result
            
        except Exception as e:
            return DissectionResult(
                protocol=ProtocolType.CHAPTER10,
                fields={},
                errors=[f"Parsing error: {str(e)}"]
            )
    
    def _find_chapter10_offset(self, raw_data: bytes) -> Optional[int]:
        """Find the offset of Chapter 10 sync pattern in raw data"""
        # Search for the sync pattern throughout the payload
        for offset in range(len(raw_data) - 1):
            if offset + 1 < len(raw_data):
                try:
                    word = struct.unpack('<H', raw_data[offset:offset+2])[0]
                    if word == self.sync_pattern:
                        # Verify we have enough space for a full header
                        if offset + 24 <= len(raw_data):
                            return offset
                except struct.error:
                    continue
        
        return None
    
    def _parse_header(self, header_data: bytes) -> Dict[str, Any]:
        """Parse Chapter 10 header"""
        if len(header_data) < 24:
            raise ValueError(f"Header too short: {len(header_data)} bytes, need 24")
            
        try:
            sync_pattern = struct.unpack('<H', header_data[0:2])[0]
            channel_id = struct.unpack('<H', header_data[2:4])[0]
            packet_length = struct.unpack('<I', header_data[4:8])[0]
            data_length = struct.unpack('<I', header_data[8:12])[0]
            data_type = struct.unpack('<H', header_data[12:14])[0]
            flags = struct.unpack('<H', header_data[14:16])[0]
            
            # Time counter is 6 bytes - combine into single value
            time_bytes = header_data[16:22]
            time_counter = int.from_bytes(time_bytes, 'little')
            
            sequence_number = struct.unpack('<H', header_data[22:24])[0]
            
            return {
                'sync_pattern': sync_pattern,
                'channel_id': channel_id,
                'packet_length': packet_length,
                'data_length': data_length,
                'data_type': data_type,
                'relative_time_counter': time_counter,
                'packet_flags': flags,
                'sequence_number': sequence_number,
                'data_type_name': self.CH10_DATA_TYPES.get(data_type, f"Unknown (0x{data_type:02x})")
            }
            
        except struct.error as e:
            raise ValueError(f"Struct unpack error: {str(e)}")
    
    def _parse_ethernet_fmt0(self, payload: bytes) -> Optional[Dict[str, Any]]:
        """Parse Ethernet Format 0 data"""
        if len(payload) < 12:
            return None
            
        try:
            # Parse intra-packet header and frame word
            iph, ts, frame_word = struct.unpack('<III', payload[:12])
            
            frame_length = frame_word & 0x3FFF
            length_error = bool(frame_word & 0x8000)
            crc_error = bool(frame_word & 0x10000)
            content_type = (frame_word >> 28) & 0x3
            
            content_types = {0: "Full MAC frame", 1: "Payload only", 2: "Reserved", 3: "Reserved"}
            
            return {
                'ethernet_iph': iph,
                'ethernet_timestamp': ts,
                'ethernet_frame_length': frame_length,
                'ethernet_length_error': length_error,
                'ethernet_crc_error': crc_error,
                'ethernet_content_type': content_types.get(content_type, "Unknown")
            }
        except:
            return None


class Chapter10Packet:
    """Represents an IRIG106 Chapter 10 packet"""
    
    def __init__(self, packet, original_frame_num: Optional[int] = None):
        """
        Initialize Chapter 10 packet from raw scapy packet
        
        Args:
            packet: Raw scapy packet
            original_frame_num: Original frame number in PCAP file
        """
        self.raw_packet = packet
        self.original_frame_num: Optional[int] = original_frame_num
        
        # Extract basic packet info
        self.timestamp = float(packet.time)
        self.packet_size = len(packet)
        
        # Extract IP/UDP info if available
        if packet.haslayer(IP) and packet.haslayer(UDP):
            ip_layer = packet[IP]
            udp_layer = packet[UDP]
            
            self.src_ip = ip_layer.src
            self.dst_ip = ip_layer.dst
            self.src_port = udp_layer.sport
            self.dst_port = udp_layer.dport
            self.payload = bytes(udp_layer.payload)
        else:
            self.src_ip = ""
            self.dst_ip = ""
            self.src_port = 0
            self.dst_port = 0
            self.payload = bytes()
        
        # Parse Chapter 10 header
        self.ch10_header = self._parse_ch10_header()
    
    def _parse_ch10_header(self) -> Optional[Dict]:
        """Parse Chapter 10 header from payload"""
        if len(self.payload) < 28:  # Minimum payload size (4-byte prefix + 24-byte Ch10 header)
            return None
        
        try:
            # Look for Ch10 sync pattern in first several bytes
            ch10_offset = None
            for offset in range(min(8, len(self.payload) - 24)):
                sync_pattern = struct.unpack('<H', self.payload[offset:offset+2])[0]
                if sync_pattern == 0xEB25:  # Ch10 sync pattern
                    ch10_offset = offset
                    break
            
            if ch10_offset is None:
                return None
            
            # Parse Chapter 10 header starting at found offset
            base = ch10_offset
            sync_pattern = struct.unpack('<H', self.payload[base:base+2])[0]
            channel_id = struct.unpack('<H', self.payload[base+2:base+4])[0]
            packet_length = struct.unpack('<I', self.payload[base+4:base+8])[0]
            data_length = struct.unpack('<I', self.payload[base+8:base+12])[0]
            header_version = self.payload[base+12]
            sequence_number = self.payload[base+13]
            packet_flags = self.payload[base+14]
            data_type = self.payload[base+15]
            rtc_low = struct.unpack('<I', self.payload[base+16:base+20])[0]
            rtc_high = struct.unpack('<H', self.payload[base+20:base+22])[0]
            checksum = struct.unpack('<H', self.payload[base+22:base+24])[0]
            
            # Store the offset for reference
            self.ch10_offset = ch10_offset
            
            return {
                'sync_pattern': f'0x{sync_pattern:04X}',
                'channel_id': channel_id,
                'packet_length': packet_length,
                'data_length': data_length,
                'header_version': header_version,
                'sequence_number': sequence_number,
                'packet_flags': f'0x{packet_flags:02X}',
                'data_type': f'0x{data_type:02X}',
                'rtc_low': rtc_low,
                'rtc_high': rtc_high,
                'checksum': f'0x{checksum:04X}',
                'rtc_timestamp': (rtc_high << 32) | rtc_low,
                'ch10_offset': ch10_offset
            }
        except (struct.error, IndexError):
            return None
    
    def get_data_payload(self) -> Optional[bytes]:
        """Extract the data payload from the Chapter 10 packet"""
        if not self.ch10_header:
            return None
        
        # Data starts after the 24-byte Chapter 10 header
        data_start = self.ch10_offset + 24
        data_length = self.ch10_header['data_length']
        
        if data_start + data_length > len(self.payload):
            return None
        
        return self.payload[data_start:data_start + data_length]


# Data decoders and related classes would go here, extracted from chapter10_packet.py
# For brevity, I'll include the key classes but the full implementation would include
# all the decoder classes (AnalogDecoder, PCMDecoder, etc.)

@dataclass 
class DecodedData:
    """Base class for decoded Chapter 10 data"""
    
    def __init__(self, data_type: str, channel_data: Dict[str, np.ndarray], 
                 timestamps: Optional[np.ndarray] = None, metadata: Optional[Dict] = None):
        self.data_type = data_type
        self.channel_data = channel_data
        self.timestamps = timestamps
        self.metadata = metadata or {}
    
    def get_channels(self) -> List[str]:
        """Get list of available channels"""
        return list(self.channel_data.keys())
    
    def get_channel_data(self, channel: str) -> Optional[np.ndarray]:
        """Get data for a specific channel"""
        return self.channel_data.get(channel)


class DataDecoder(ABC):
    """Abstract base class for Chapter 10 data decoders"""
    
    def __init__(self, tmats_scaling_dict: Optional[Dict] = None):
        self.tmats_scaling_dict = tmats_scaling_dict or {}
    
    @abstractmethod
    def decode(self, data_payload: bytes, ch10_header: Dict) -> Optional[DecodedData]:
        """Decode the data payload"""
        pass