"""Canonical protocol specification data structures.

This module provides the single source of truth for protocol specification
definitions used across Oscura's reverse engineering, validation, and session
management components.

Usage:
    For reverse engineering:
        >>> from oscura.core.protocol_spec import ProtocolSpec, FieldSpec
        >>> spec = ProtocolSpec(
        ...     name="UART Protocol",
        ...     baud_rate=115200,
        ...     frame_format="8N1",
        ...     fields=[
        ...         FieldSpec(name="header", offset=0, size=2, field_type="constant"),
        ...         FieldSpec(name="payload", offset=2, size=8, field_type="bytes"),
        ...     ]
        ... )

    For validation/replay:
        >>> from oscura.core.protocol_spec import ProtocolSpec
        >>> spec = ProtocolSpec(
        ...     name="Test Protocol",
        ...     checksum_algorithm="crc16",
        ...     expected_response_time=0.1
        ... )

    For session analysis:
        >>> from oscura.core.protocol_spec import ProtocolSpec
        >>> spec = ProtocolSpec(
        ...     name="IoT Protocol",
        ...     fields=[field1, field2],
        ...     crc_info={"polynomial": 0x1021}
        ... )

Design:
    This consolidates three previously separate ProtocolSpec definitions:
    - workflows/reverse_engineering.py (reverse engineering context)
    - sessions/blackbox.py (session analysis context)
    - validation/replay.py (validation/testing context)

    The unified ProtocolSpec supports all use cases through optional fields.
"""

from __future__ import annotations

from dataclasses import dataclass, field
from typing import Any


@dataclass
class FieldSpec:
    """Specification for a protocol field.

    Describes the structure, position, and type of a field within a protocol
    frame or message.

    Attributes:
        name: Field name (e.g., "header", "payload", "checksum").
        offset: Byte offset in frame/message (0-indexed).
        size: Field size in bytes. Can be int for fixed-size fields, or
            string expression for variable-size (e.g., "length - 4").
        field_type: Field type classification:
            - "constant": Fixed value (e.g., sync pattern, magic number)
            - "counter": Incrementing/decrementing sequence number
            - "checksum": Error detection field
            - "length": Message/payload length field
            - "data"/"bytes": Variable data payload
            - "uint8"/"uint16"/"uint32": Unsigned integer types
            - "int8"/"int16"/"int32": Signed integer types
            - "unknown": Type not yet determined
        value: Example value or constant (for constant fields).
            Can be bytes, int, str (hex), or None.

    Example:
        >>> # Fixed constant header
        >>> header = FieldSpec(
        ...     name="sync",
        ...     offset=0,
        ...     size=2,
        ...     field_type="constant",
        ...     value=b"\\xaa\\x55"
        ... )
        >>> # Variable-length payload
        >>> payload = FieldSpec(
        ...     name="data",
        ...     offset=4,
        ...     size="length - 6",
        ...     field_type="bytes"
        ... )
    """

    name: str
    offset: int
    size: int | str
    field_type: str
    value: Any = None


@dataclass
class ProtocolSpec:
    """Unified protocol specification for reverse engineering, validation, and analysis.

    This is the canonical protocol specification data structure used across
    Oscura. It consolidates fields from multiple contexts into a single,
    comprehensive specification.

    Attributes:
        name: Protocol name/identifier (e.g., "UART", "Custom IoT Protocol").

        # Physical layer (reverse engineering context)
        baud_rate: Communication baud rate in bits/second (optional).
            Used for serial protocols (UART, RS-232, etc.).
        frame_format: Frame format string (optional, e.g., "8N1", "7E1").
            Format: "<data_bits><parity><stop_bits>".

        # Frame structure (reverse engineering context)
        sync_pattern: Frame synchronization pattern as hex string (optional).
            Example: "aa55" for bytes [0xAA, 0x55].
        frame_length: Fixed frame length in bytes, or None for variable-length.
        fields: List of field specifications describing frame structure.

        # Error detection (both contexts)
        checksum_type: Checksum/CRC algorithm name (optional).
            Common values: "xor", "sum8", "crc8", "crc16", "crc32", "none".
        checksum_algorithm: Alias for checksum_type (validation context).
        checksum_position: Byte position of checksum field.
            Use -1 for last byte (most common).

        # Timing constraints (validation context)
        expected_response_time: Expected response time in seconds (optional).
            Used during replay validation to verify timing correctness.
        timing_tolerance: Timing tolerance as fraction (0.0-1.0).
            Example: 0.2 = 20% tolerance on response time.
        require_response: Whether responses are required for each message.

        # Advanced analysis (session context)
        state_machine: State machine object (optional, from state inference).
        crc_info: Detailed CRC/checksum parameters (optional).
            Dictionary with keys: polynomial, width, init, xor_out, reflect_in,
            reflect_out, algorithm_name, confidence.
        constants: Dictionary of constant values found in protocol (optional).
        metadata: Additional protocol metadata (optional).

        # Quality metrics
        confidence: Overall confidence score (0.0-1.0) for inferred protocols.
            1.0 = fully verified, <0.7 = low confidence.
        message_format: Human-readable message format description (optional).

    Example:
        >>> # Complete specification for reverse-engineered protocol
        >>> spec = ProtocolSpec(
        ...     name="IoT Device Protocol v2",
        ...     baud_rate=115200,
        ...     frame_format="8N1",
        ...     sync_pattern="aa55",
        ...     frame_length=16,
        ...     fields=[
        ...         FieldSpec("sync", 0, 2, "constant", b"\\xaa\\x55"),
        ...         FieldSpec("length", 2, 1, "uint8"),
        ...         FieldSpec("payload", 3, "length-5", "bytes"),
        ...         FieldSpec("checksum", -1, 2, "checksum"),
        ...     ],
        ...     checksum_type="crc16",
        ...     checksum_position=-1,
        ...     confidence=0.92,
        ... )

        >>> # Minimal specification for validation
        >>> spec = ProtocolSpec(
        ...     name="Test Protocol",
        ...     checksum_algorithm="xor",
        ...     expected_response_time=0.05,
        ...     timing_tolerance=0.1,
        ... )

    References:
        - workflows/reverse_engineering.py: Original reverse engineering spec
        - sessions/blackbox.py: Session analysis spec
        - validation/replay.py: Validation/replay spec
    """

    name: str

    # Physical layer
    baud_rate: float | None = None
    frame_format: str = ""

    # Frame structure
    sync_pattern: str = ""
    frame_length: int | None = None
    fields: list[FieldSpec] = field(default_factory=list)

    # Error detection
    checksum_type: str | None = None
    checksum_algorithm: str = "none"  # Alias for validation context
    checksum_position: int | None = None

    # Timing (validation)
    expected_response_time: float = 0.1
    timing_tolerance: float = 0.2
    require_response: bool = True

    # Advanced analysis
    state_machine: Any = None
    crc_info: dict[str, Any] = field(default_factory=dict)
    constants: dict[str, Any] = field(default_factory=dict)
    metadata: dict[str, Any] = field(default_factory=dict)

    # Quality metrics
    confidence: float = 1.0
    message_format: str = ""

    def __post_init__(self) -> None:
        """Normalize and validate protocol specification after initialization."""
        # Sync checksum_type and checksum_algorithm
        if self.checksum_type is None and self.checksum_algorithm != "none":
            self.checksum_type = self.checksum_algorithm
        elif self.checksum_type is not None and self.checksum_algorithm == "none":
            self.checksum_algorithm = self.checksum_type

        # Validate confidence range
        if not 0.0 <= self.confidence <= 1.0:
            raise ValueError(f"confidence must be in range [0.0, 1.0], got {self.confidence}")

        # Validate timing tolerance
        if not 0.0 <= self.timing_tolerance <= 1.0:
            raise ValueError(
                f"timing_tolerance must be in range [0.0, 1.0], got {self.timing_tolerance}"
            )


__all__ = ["FieldSpec", "ProtocolSpec"]
