#!/usr/bin/env python3
"""
FibPi Encoded Domain Operations - Computations on encoded data without full decode

Perform analysis, pattern matching, and transformations directly on FibPi-encoded
files without requiring full decompression.

Use Cases:
    - Security scanning: Check for malware signatures without decompressing
    - Dedup detection: Compare files by anchor sets (O(n) anchors vs O(n) bytes)
    - Entropy analysis: Find encrypted/compressed regions for forensics
    - Incremental backup: Only sync changed chunks (anchor mismatch)

Usage:
    # Search for pattern
    python3 fibpi_encoded_ops.py grep data.fibpi "error" --context 20

    # Entropy analysis
    python3 fibpi_encoded_ops.py entropy data.fibpi --threshold 7.0

    # Diff two files
    python3 fibpi_encoded_ops.py diff file1.fibpi file2.fibpi

    # Extract strings
    python3 fibpi_encoded_ops.py strings data.fibpi --min-length 8
"""

import os
import re
import sys
import math
import json
import hashlib
from pathlib import Path
from typing import List, Tuple, Optional, Dict, Set, Any, Iterator, NamedTuple
from dataclasses import dataclass
from collections import Counter

# Import base components - try relative, then absolute, then fallback
FibPiRandomAccessIndexed = None
ChunkInfo = None

try:
    # Try relative import (package context)
    from .fibpi_sha_anchor import (
        HEADER_MAGIC, ANCHOR_PREFIX, ANCHOR_LEN, CHUNK_PREFIX, CHUNK_SUFFIX,
        sha256_hex
    )
    from .fibpi_index import FibPiRandomAccessIndexed, ChunkInfo
except ImportError:
    try:
        # Try absolute import (direct execution)
        from fibpi_sha_anchor import (
            HEADER_MAGIC, ANCHOR_PREFIX, ANCHOR_LEN, CHUNK_PREFIX, CHUNK_SUFFIX,
            sha256_hex
        )
        from fibpi_index import FibPiRandomAccessIndexed, ChunkInfo
    except ImportError:
        # Fallback for standalone testing
        HEADER_MAGIC = b"FIBPI3D_SHA_v1"
        ANCHOR_PREFIX = b"::SHA::"
        ANCHOR_LEN = 64
        CHUNK_PREFIX = b"::B64::"
        CHUNK_SUFFIX = b"::END::"

        def sha256_hex(data: bytes) -> str:
            return hashlib.sha256(data).hexdigest()


# Known file signatures for header detection
FILE_SIGNATURES = {
    b'\x7fELF': 'ELF (Executable)',
    b'\x89PNG\r\n\x1a\n': 'PNG Image',
    b'\xff\xd8\xff': 'JPEG Image',
    b'GIF87a': 'GIF87a Image',
    b'GIF89a': 'GIF89a Image',
    b'PK\x03\x04': 'ZIP Archive',
    b'PK\x05\x06': 'ZIP (empty)',
    b'\x1f\x8b': 'GZIP',
    b'BZh': 'BZIP2',
    b'\xfd7zXZ\x00': 'XZ',
    b'GGUF': 'GGUF (LLM Model)',
    b'GGML': 'GGML (Legacy LLM)',
    b'\x00\x00\x00\x1cftyp': 'MP4 Video',
    b'RIFF': 'RIFF (WAV/AVI)',
    b'%PDF': 'PDF Document',
    b'<!DOCTYPE html': 'HTML Document',
    b'<html': 'HTML Document',
    b'<?xml': 'XML Document',
    b'{\n': 'JSON (formatted)',
    b'{"': 'JSON (compact)',
    b'SQLite format 3': 'SQLite Database',
    b'\xca\xfe\xba\xbe': 'Java Class/Mach-O Fat',
    b'\xfe\xed\xfa\xce': 'Mach-O 32-bit',
    b'\xfe\xed\xfa\xcf': 'Mach-O 64-bit',
    b'MZ': 'DOS/PE Executable',
    b'\x50\x4b\x03\x04\x14\x00\x06\x00': 'DOCX/XLSX/PPTX',
}


@dataclass
class Match:
    """Represents a pattern match result."""
    chunk_index: int
    offset_in_chunk: int
    global_offset: int
    matched_text: bytes
    context_before: bytes
    context_after: bytes

    def __str__(self):
        ctx = self.context_before + b'>>>' + self.matched_text + b'<<<' + self.context_after
        return f"chunk[{self.chunk_index}]:{self.offset_in_chunk} ({self.global_offset}): {ctx!r}"


@dataclass
class EntropyResult:
    """Entropy analysis result for a chunk."""
    chunk_index: int
    entropy: float
    byte_count: int
    is_high_entropy: bool

    @property
    def entropy_category(self) -> str:
        if self.entropy < 3.0:
            return "low (text/structured)"
        elif self.entropy < 5.0:
            return "medium (mixed)"
        elif self.entropy < 7.0:
            return "high (binary/code)"
        else:
            return "very-high (compressed/encrypted)"


@dataclass
class StringMatch:
    """Extracted string from encoded data."""
    chunk_index: int
    offset: int
    global_offset: int
    string: str
    length: int


class EncodedDomainOperations:
    """
    Operations on FibPi-encoded data without full decode.

    Each operation only decodes the minimum necessary chunks.
    """

    def __init__(self, fibpi_path: Path):
        """
        Initialize with path to FibPi-encoded file.

        Args:
            fibpi_path: Path to .fibpi file
        """
        self.filepath = Path(fibpi_path)
        self.ra = FibPiRandomAccessIndexed(fibpi_path)
        self.meta = self.ra.get_metadata()

        # Cache for decoded chunks (LRU-style)
        self._chunk_cache: Dict[int, bytes] = {}
        self._cache_max_size = 16

    def _get_chunk(self, idx: int, use_cache: bool = True) -> bytes:
        """Get a chunk with optional caching."""
        if use_cache and idx in self._chunk_cache:
            return self._chunk_cache[idx]

        chunk = self.ra.get_chunk_by_index(idx, verify=False)

        if use_cache:
            self._chunk_cache[idx] = chunk
            # Simple LRU eviction
            if len(self._chunk_cache) > self._cache_max_size:
                oldest = next(iter(self._chunk_cache))
                del self._chunk_cache[oldest]

        return chunk

    def _get_global_offset(self, chunk_idx: int) -> int:
        """Get the global byte offset for start of a chunk."""
        positions = self.meta.get('fibonacci_positions', [])
        if chunk_idx == 0:
            return 0
        if chunk_idx <= len(positions):
            return positions[chunk_idx - 1]
        return 0

    # === Pattern Matching ===

    def grep_encoded(self, pattern: bytes, context: int = 0) -> List[Match]:
        """
        Search for pattern across all chunks.

        Args:
            pattern: Byte pattern to search for
            context: Number of context bytes before/after match

        Returns:
            List of Match objects
        """
        matches = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            global_base = self._get_global_offset(idx)

            offset = 0
            while True:
                pos = chunk.find(pattern, offset)
                if pos == -1:
                    break

                # Extract context
                ctx_start = max(0, pos - context)
                ctx_end = min(len(chunk), pos + len(pattern) + context)

                matches.append(Match(
                    chunk_index=idx,
                    offset_in_chunk=pos,
                    global_offset=global_base + pos,
                    matched_text=pattern,
                    context_before=chunk[ctx_start:pos],
                    context_after=chunk[pos + len(pattern):ctx_end]
                ))

                offset = pos + 1

        return matches

    def regex_encoded(self, pattern: str, flags: int = 0) -> List[Match]:
        """
        Regex search across all chunks.

        Note: Does not match across chunk boundaries.

        Args:
            pattern: Regex pattern (string)
            flags: re module flags (re.IGNORECASE, etc.)

        Returns:
            List of Match objects
        """
        compiled = re.compile(pattern.encode() if isinstance(pattern, str) else pattern, flags)
        matches = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            global_base = self._get_global_offset(idx)

            for m in compiled.finditer(chunk):
                matches.append(Match(
                    chunk_index=idx,
                    offset_in_chunk=m.start(),
                    global_offset=global_base + m.start(),
                    matched_text=m.group(),
                    context_before=chunk[max(0, m.start()-20):m.start()],
                    context_after=chunk[m.end():min(len(chunk), m.end()+20)]
                ))

        return matches

    def count_pattern(self, pattern: bytes) -> int:
        """Count occurrences of pattern without returning matches."""
        count = 0
        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            count += chunk.count(pattern)
        return count

    # === Statistical Analysis ===

    def calculate_entropy(self, data: bytes) -> float:
        """Calculate Shannon entropy of byte data."""
        if not data:
            return 0.0

        counts = Counter(data)
        length = len(data)
        entropy = 0.0

        for count in counts.values():
            if count > 0:
                p = count / length
                entropy -= p * math.log2(p)

        return entropy

    def chunk_entropy_map(self) -> Dict[int, EntropyResult]:
        """
        Calculate Shannon entropy per chunk.

        Returns:
            Dict mapping chunk index to EntropyResult
        """
        results = {}

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            entropy = self.calculate_entropy(chunk)

            results[idx] = EntropyResult(
                chunk_index=idx,
                entropy=entropy,
                byte_count=len(chunk),
                is_high_entropy=(entropy >= 7.0)
            )

        return results

    def find_high_entropy_chunks(self, threshold: float = 7.5) -> List[int]:
        """
        Identify compressed/encrypted regions.

        Args:
            threshold: Entropy threshold (7.5 = highly compressed/encrypted)

        Returns:
            List of chunk indices with entropy >= threshold
        """
        high_entropy = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            entropy = self.calculate_entropy(chunk)
            if entropy >= threshold:
                high_entropy.append(idx)

        return high_entropy

    def find_low_entropy_chunks(self, threshold: float = 4.0) -> List[int]:
        """Find chunks with low entropy (likely text/structured data)."""
        low_entropy = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            entropy = self.calculate_entropy(chunk)
            if entropy <= threshold:
                low_entropy.append(idx)

        return low_entropy

    def entropy_histogram(self, bins: int = 8) -> Dict[str, int]:
        """
        Create histogram of entropy distribution across chunks.

        Returns:
            Dict mapping entropy range strings to chunk counts
        """
        histogram = {}
        bin_size = 8.0 / bins

        for i in range(bins):
            low = i * bin_size
            high = (i + 1) * bin_size
            histogram[f"{low:.1f}-{high:.1f}"] = 0

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            entropy = self.calculate_entropy(chunk)
            bin_idx = min(int(entropy / bin_size), bins - 1)
            low = bin_idx * bin_size
            high = (bin_idx + 1) * bin_size
            histogram[f"{low:.1f}-{high:.1f}"] += 1

        return histogram

    # === Structural Analysis ===

    def detect_headers(self) -> List[Tuple[int, int, str]]:
        """
        Detect known file headers in encoded data.

        Returns:
            List of (chunk_index, offset, file_type) tuples
        """
        detected = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)

            for signature, file_type in FILE_SIGNATURES.items():
                offset = 0
                while True:
                    pos = chunk.find(signature, offset)
                    if pos == -1:
                        break
                    detected.append((idx, pos, file_type))
                    offset = pos + 1

        return detected

    def detect_embedded_files(self) -> List[Dict[str, Any]]:
        """
        Detect embedded files by their headers.

        Returns more detailed information about potential embedded files.
        """
        embedded = []
        headers = self.detect_headers()

        for chunk_idx, offset, file_type in headers:
            chunk = self._get_chunk(chunk_idx)
            global_offset = self._get_global_offset(chunk_idx) + offset

            # Try to determine size for some formats
            size_hint = None
            if file_type == 'PNG Image' and len(chunk) > offset + 8:
                # PNG has IHDR chunk with dimensions
                size_hint = "dimensions in IHDR"
            elif file_type == 'JPEG Image':
                size_hint = "variable (find FFD9 marker)"

            embedded.append({
                'chunk_index': chunk_idx,
                'offset_in_chunk': offset,
                'global_offset': global_offset,
                'file_type': file_type,
                'header_bytes': chunk[offset:offset+16].hex(),
                'size_hint': size_hint
            })

        return embedded

    def extract_strings(self, min_len: int = 4, encoding: str = 'ascii') -> List[StringMatch]:
        """
        Extract printable strings from encoded data.

        Args:
            min_len: Minimum string length to extract
            encoding: String encoding to try ('ascii', 'utf-8')

        Returns:
            List of StringMatch objects
        """
        strings = []

        if encoding == 'ascii':
            # ASCII printable: 0x20-0x7E
            pattern = re.compile(rb'[\x20-\x7e]{' + str(min_len).encode() + rb',}')
        else:
            # UTF-8 with BMP characters
            pattern = re.compile(rb'[\x20-\x7e\xc0-\xff]{' + str(min_len).encode() + rb',}')

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            global_base = self._get_global_offset(idx)

            for m in pattern.finditer(chunk):
                try:
                    decoded = m.group().decode(encoding, errors='replace')
                    strings.append(StringMatch(
                        chunk_index=idx,
                        offset=m.start(),
                        global_offset=global_base + m.start(),
                        string=decoded,
                        length=len(decoded)
                    ))
                except Exception:
                    pass

        return strings

    def extract_urls(self) -> List[Tuple[int, int, str]]:
        """Extract URLs from encoded data."""
        url_pattern = re.compile(rb'https?://[^\s<>"\']+')
        urls = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            for m in url_pattern.finditer(chunk):
                try:
                    url = m.group().decode('utf-8', errors='replace')
                    urls.append((idx, m.start(), url))
                except Exception:
                    pass

        return urls

    def extract_emails(self) -> List[Tuple[int, int, str]]:
        """Extract email addresses from encoded data."""
        email_pattern = re.compile(rb'[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}')
        emails = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            for m in email_pattern.finditer(chunk):
                try:
                    email = m.group().decode('utf-8', errors='replace')
                    emails.append((idx, m.start(), email))
                except Exception:
                    pass

        return emails

    # === Diff Operations ===

    def diff_anchors(self, other: 'EncodedDomainOperations') -> Dict[str, Any]:
        """
        Compare two FibPi files by anchor without full decode.

        This is an O(n) operation on anchor count, not byte count.

        Returns:
            Dict with common, only_self, only_other anchor sets
        """
        my_anchors = set(self.ra.list_anchors())
        their_anchors = set(other.ra.list_anchors())

        common = my_anchors & their_anchors
        only_self = my_anchors - their_anchors
        only_other = their_anchors - my_anchors

        return {
            'common': list(common),
            'only_self': list(only_self),
            'only_other': list(only_other),
            'self_count': len(my_anchors),
            'other_count': len(their_anchors),
            'common_count': len(common),
            'similarity': len(common) / max(len(my_anchors), len(their_anchors)) if my_anchors or their_anchors else 1.0
        }

    def diff_detailed(self, other: 'EncodedDomainOperations') -> Dict[str, Any]:
        """
        Detailed diff showing which chunks changed.

        Returns:
            Dict with change analysis
        """
        diff = self.diff_anchors(other)

        # Map anchors to chunk indices
        my_anchor_map = {anchor: idx for idx, anchor in enumerate(self.ra.list_anchors())}
        their_anchor_map = {anchor: idx for idx, anchor in enumerate(other.ra.list_anchors())}

        changed_self = [my_anchor_map[a] for a in diff['only_self'] if a in my_anchor_map]
        changed_other = [their_anchor_map[a] for a in diff['only_other'] if a in their_anchor_map]

        return {
            'anchor_diff': diff,
            'changed_chunks_self': sorted(changed_self),
            'changed_chunks_other': sorted(changed_other),
            'unchanged_chunks': len(diff['common']),
            'percent_changed': 100 * (1 - diff['similarity'])
        }

    # === Homomorphic-Style Operations ===

    def xor_chunk(self, idx: int, key: bytes) -> bytes:
        """
        XOR a chunk without decoding the whole file.

        Args:
            idx: Chunk index
            key: XOR key (will be repeated to match chunk length)

        Returns:
            XORed chunk data
        """
        chunk = self._get_chunk(idx)
        key_repeated = (key * ((len(chunk) // len(key)) + 1))[:len(chunk)]
        return bytes(a ^ b for a, b in zip(chunk, key_repeated))

    def compute_chunk_hash(self, idx: int, algorithm: str = 'sha256') -> str:
        """
        Compute hash of a single chunk.

        Args:
            idx: Chunk index
            algorithm: Hash algorithm ('md5', 'sha1', 'sha256', 'sha512')

        Returns:
            Hex hash string
        """
        chunk = self._get_chunk(idx)

        if algorithm == 'md5':
            return hashlib.md5(chunk).hexdigest()
        elif algorithm == 'sha1':
            return hashlib.sha1(chunk).hexdigest()
        elif algorithm == 'sha256':
            return hashlib.sha256(chunk).hexdigest()
        elif algorithm == 'sha512':
            return hashlib.sha512(chunk).hexdigest()
        else:
            raise ValueError(f"Unknown hash algorithm: {algorithm}")

    def compute_rolling_hashes(self, window_size: int = 64) -> List[Tuple[int, int, int]]:
        """
        Compute rolling hashes across chunks for similarity detection.

        Uses a simple polynomial rolling hash.

        Returns:
            List of (chunk_idx, offset, hash_value) tuples
        """
        BASE = 256
        MOD = 2**61 - 1  # Large prime
        hashes = []

        for idx in range(self.ra.get_chunk_count()):
            chunk = self._get_chunk(idx)
            if len(chunk) < window_size:
                continue

            # Initial hash
            h = 0
            base_power = pow(BASE, window_size - 1, MOD)

            for i in range(window_size):
                h = (h * BASE + chunk[i]) % MOD

            hashes.append((idx, 0, h))

            # Rolling
            for i in range(window_size, len(chunk)):
                h = (h - chunk[i - window_size] * base_power) % MOD
                h = (h * BASE + chunk[i]) % MOD
                hashes.append((idx, i - window_size + 1, h))

        return hashes

    # === Summary Statistics ===

    def get_summary(self) -> Dict[str, Any]:
        """Get comprehensive summary of encoded file."""
        entropy_map = self.chunk_entropy_map()
        avg_entropy = sum(r.entropy for r in entropy_map.values()) / len(entropy_map) if entropy_map else 0

        high_entropy = [idx for idx, r in entropy_map.items() if r.is_high_entropy]
        total_bytes = sum(r.byte_count for r in entropy_map.values())

        headers = self.detect_headers()

        return {
            'file': str(self.filepath),
            'total_chunks': self.ra.get_chunk_count(),
            'total_bytes': total_bytes,
            'average_entropy': round(avg_entropy, 3),
            'high_entropy_chunks': len(high_entropy),
            'detected_headers': len(headers),
            'unique_file_types': list(set(h[2] for h in headers)),
            'metadata': self.meta
        }


def main():
    import argparse

    parser = argparse.ArgumentParser(description='FibPi Encoded Domain Operations')
    subparsers = parser.add_subparsers(dest='command', help='Commands')

    # grep
    grep_parser = subparsers.add_parser('grep', help='Search for pattern')
    grep_parser.add_argument('file', help='FibPi file')
    grep_parser.add_argument('pattern', help='Pattern to search')
    grep_parser.add_argument('-c', '--context', type=int, default=20,
                             help='Context bytes (default: 20)')
    grep_parser.add_argument('--count', action='store_true',
                             help='Just count matches')

    # entropy
    entropy_parser = subparsers.add_parser('entropy', help='Entropy analysis')
    entropy_parser.add_argument('file', help='FibPi file')
    entropy_parser.add_argument('-t', '--threshold', type=float, default=7.0,
                                help='High entropy threshold (default: 7.0)')
    entropy_parser.add_argument('--histogram', action='store_true',
                                help='Show histogram')

    # diff
    diff_parser = subparsers.add_parser('diff', help='Diff two files by anchors')
    diff_parser.add_argument('file1', help='First FibPi file')
    diff_parser.add_argument('file2', help='Second FibPi file')
    diff_parser.add_argument('--detailed', action='store_true',
                             help='Show detailed diff')

    # strings
    strings_parser = subparsers.add_parser('strings', help='Extract strings')
    strings_parser.add_argument('file', help='FibPi file')
    strings_parser.add_argument('-n', '--min-length', type=int, default=4,
                                help='Minimum string length (default: 4)')

    # headers
    headers_parser = subparsers.add_parser('headers', help='Detect file headers')
    headers_parser.add_argument('file', help='FibPi file')

    # summary
    summary_parser = subparsers.add_parser('summary', help='File summary')
    summary_parser.add_argument('file', help='FibPi file')

    args = parser.parse_args()

    if not args.command:
        parser.print_help()
        return 1

    if args.command == 'grep':
        ops = EncodedDomainOperations(args.file)
        pattern = args.pattern.encode()

        if args.count:
            count = ops.count_pattern(pattern)
            print(f"Found {count} occurrences of {args.pattern!r}")
        else:
            matches = ops.grep_encoded(pattern, context=args.context)
            print(f"Found {len(matches)} matches for {args.pattern!r}:")
            for m in matches[:100]:  # Limit output
                print(f"  {m}")
            if len(matches) > 100:
                print(f"  ... and {len(matches) - 100} more")

    elif args.command == 'entropy':
        ops = EncodedDomainOperations(args.file)

        if args.histogram:
            hist = ops.entropy_histogram()
            print("Entropy histogram:")
            for range_str, count in hist.items():
                bar = '#' * count
                print(f"  {range_str}: {count:3d} {bar}")
        else:
            entropy_map = ops.chunk_entropy_map()
            high = [idx for idx, r in entropy_map.items() if r.entropy >= args.threshold]

            print(f"Entropy analysis for {args.file}:")
            print(f"  Total chunks: {len(entropy_map)}")
            print(f"  High entropy (>={args.threshold}): {len(high)}")

            if high:
                print(f"  High entropy chunks: {high[:20]}")
                if len(high) > 20:
                    print(f"    ... and {len(high) - 20} more")

    elif args.command == 'diff':
        ops1 = EncodedDomainOperations(args.file1)
        ops2 = EncodedDomainOperations(args.file2)

        if args.detailed:
            diff = ops1.diff_detailed(ops2)
            print(f"Detailed diff:")
            print(f"  Unchanged chunks: {diff['unchanged_chunks']}")
            print(f"  Changed in file1: {diff['changed_chunks_self']}")
            print(f"  Changed in file2: {diff['changed_chunks_other']}")
            print(f"  Percent changed: {diff['percent_changed']:.1f}%")
        else:
            diff = ops1.diff_anchors(ops2)
            print(f"Anchor diff:")
            print(f"  File1 chunks: {diff['self_count']}")
            print(f"  File2 chunks: {diff['other_count']}")
            print(f"  Common: {diff['common_count']}")
            print(f"  Only in file1: {len(diff['only_self'])}")
            print(f"  Only in file2: {len(diff['only_other'])}")
            print(f"  Similarity: {diff['similarity']*100:.1f}%")

    elif args.command == 'strings':
        ops = EncodedDomainOperations(args.file)
        strings = ops.extract_strings(min_len=args.min_length)

        print(f"Extracted {len(strings)} strings (min length {args.min_length}):")
        for s in strings[:100]:
            print(f"  [{s.chunk_index}:{s.offset}] {s.string[:80]}{'...' if len(s.string) > 80 else ''}")
        if len(strings) > 100:
            print(f"  ... and {len(strings) - 100} more")

    elif args.command == 'headers':
        ops = EncodedDomainOperations(args.file)
        headers = ops.detect_headers()

        print(f"Detected {len(headers)} file headers:")
        for chunk_idx, offset, file_type in headers:
            print(f"  chunk[{chunk_idx}]:{offset} - {file_type}")

    elif args.command == 'summary':
        ops = EncodedDomainOperations(args.file)
        summary = ops.get_summary()

        print(f"Summary for {summary['file']}:")
        print(f"  Total chunks: {summary['total_chunks']}")
        print(f"  Total bytes: {summary['total_bytes']:,}")
        print(f"  Average entropy: {summary['average_entropy']}")
        print(f"  High entropy chunks: {summary['high_entropy_chunks']}")
        print(f"  Detected headers: {summary['detected_headers']}")
        if summary['unique_file_types']:
            print(f"  File types: {', '.join(summary['unique_file_types'])}")

    return 0


if __name__ == '__main__':
    exit(main())
