"""
Stegasoo Constants and Configuration (v4.2.0 - Performance & Compression)

Central location for all magic numbers, limits, and crypto parameters.
All version numbers, limits, and configuration values should be defined here.

CHANGES in v4.2.0:
- Added zstd compression for QR codes (better ratio than zlib)
- RSA key size capped at 3072 bits (4096 too large for QR codes)
- Progress bar improvements for encode/decode operations
- File auto-expire increased to 10 minutes

CHANGES in v4.0.2:
- Added Web UI authentication with SQLite3 user storage
- Added optional HTTPS with auto-generated self-signed certificates
- UI improvements for QR preview panels and PIN/channel columns

BREAKING CHANGES in v4.0.0:
- Added channel key support for deployment/group isolation
- FORMAT_VERSION bumped to 5 (adds flags byte to header)
- Header size increased by 1 byte for flags

BREAKING CHANGES in v3.2.0:
- Removed date dependency from cryptographic operations
- Renamed day_phrase → passphrase throughout codebase
"""

import importlib.resources
from pathlib import Path

# ============================================================================
# VERSION
# ============================================================================

__version__ = "4.2.1"

# ============================================================================
# FILE FORMAT
# ============================================================================

MAGIC_HEADER = b"\x89ST3"

# FORMAT VERSION HISTORY:
# Version 1-3: Date-dependent encryption (v3.0.x - v3.1.x)
# Version 4: Date-independent encryption (v3.2.0)
# Version 5: Channel key support (v4.0.0) - adds flags byte to header
# Version 6: HKDF per-message key derivation (v4.4.0) - adds message nonce to header
FORMAT_VERSION = 6
FORMAT_VERSION_LEGACY = 5  # For backward-compatible decryption

# Payload type markers
PAYLOAD_TEXT = 0x01
PAYLOAD_FILE = 0x02

# ============================================================================
# CRYPTO PARAMETERS
# ============================================================================

SALT_SIZE = 32
IV_SIZE = 12
TAG_SIZE = 16

# Argon2 parameters (memory-hard KDF)
ARGON2_TIME_COST = 4
ARGON2_MEMORY_COST = 256 * 1024  # 256 MB
ARGON2_PARALLELISM = 4

# PBKDF2 fallback parameters
PBKDF2_ITERATIONS = 600000

# HKDF per-message key derivation (v4.4.0 / FORMAT_VERSION 6)
MESSAGE_NONCE_SIZE = 16  # 128-bit random nonce per message
HKDF_INFO_ENCRYPT = b"stegasoo-v6-encrypt"  # HKDF info for encryption key
HKDF_INFO_PIXEL = b"stegasoo-v6-pixel"  # HKDF info for pixel selection key (reserved)

# ============================================================================
# INPUT LIMITS
# ============================================================================

MAX_IMAGE_PIXELS = 24_000_000  # ~24 megapixels
MIN_IMAGE_PIXELS = 256 * 256  # Minimum viable image size

MAX_MESSAGE_SIZE = 250_000  # 250 KB (text messages)
MAX_MESSAGE_CHARS = 250_000  # Alias for clarity in templates
MIN_MESSAGE_LENGTH = 1  # Minimum message length
MAX_MESSAGE_LENGTH = MAX_MESSAGE_SIZE  # Alias for consistency

MAX_PAYLOAD_SIZE = MAX_MESSAGE_SIZE  # Maximum payload size (alias)
MAX_FILENAME_LENGTH = 255  # Max filename length to store

# File size limits
MAX_FILE_SIZE = 30 * 1024 * 1024  # 30MB total file size
MAX_FILE_PAYLOAD_SIZE = 2 * 1024 * 1024  # 2MB payload
MAX_UPLOAD_SIZE = 30 * 1024 * 1024  # 30MB max upload (Flask)

# PIN configuration
MIN_PIN_LENGTH = 6
MAX_PIN_LENGTH = 9
DEFAULT_PIN_LENGTH = 6

# Passphrase configuration (v3.2.0: renamed from PHRASE to PASSPHRASE)
# Increased defaults to compensate for removed date entropy (~33 bits)
MIN_PASSPHRASE_WORDS = 3
MAX_PASSPHRASE_WORDS = 12
DEFAULT_PASSPHRASE_WORDS = 4  # Increased from 3 (was DEFAULT_PHRASE_WORDS)
RECOMMENDED_PASSPHRASE_WORDS = 4  # Best practice guideline

# Legacy aliases for backward compatibility during transition
MIN_PHRASE_WORDS = MIN_PASSPHRASE_WORDS
MAX_PHRASE_WORDS = MAX_PASSPHRASE_WORDS
DEFAULT_PHRASE_WORDS = DEFAULT_PASSPHRASE_WORDS

# RSA configuration
MIN_RSA_BITS = 2048
VALID_RSA_SIZES = (2048, 3072)  # 4096 removed - too large for QR codes
DEFAULT_RSA_BITS = 2048

MIN_KEY_PASSWORD_LENGTH = 8

# ============================================================================
# WEB/API CONFIGURATION
# ============================================================================

# Temporary file storage
TEMP_FILE_EXPIRY = 600  # 10 minutes in seconds
TEMP_FILE_EXPIRY_MINUTES = 10

# Thumbnail settings
THUMBNAIL_SIZE = (250, 250)  # Maximum dimensions for thumbnails
THUMBNAIL_QUALITY = 85

# QR Code limits
QR_MAX_BINARY = 2900  # Safe limit for binary data in QR
QR_CROP_PADDING_PERCENT = 0.1  # Default padding when cropping QR codes
QR_CROP_MIN_PADDING_PX = 10  # Minimum padding in pixels

# ============================================================================
# FILE TYPES
# ============================================================================

ALLOWED_IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "bmp", "gif"}
ALLOWED_KEY_EXTENSIONS = {"pem", "key"}

# Lossless image formats (safe for steganography)
LOSSLESS_FORMATS = {"PNG", "BMP", "TIFF"}

# Supported image formats for steganography
SUPPORTED_IMAGE_FORMATS = LOSSLESS_FORMATS

# ============================================================================
# DAYS (kept for organizational/UI purposes, not crypto)
# ============================================================================

DAY_NAMES = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")

# ============================================================================
# COMPRESSION
# ============================================================================

# Minimum payload size to attempt compression (smaller often expands)
MIN_COMPRESS_SIZE = 64

# Zlib compression level (1-9, higher = better ratio, slower)
ZLIB_COMPRESSION_LEVEL = 6

# Compression header magic bytes
COMPRESSION_MAGIC = b"\x00CMP"

# ============================================================================
# BATCH PROCESSING
# ============================================================================

# Default parallel workers for batch operations
BATCH_DEFAULT_WORKERS = 4

# Maximum parallel workers
BATCH_MAX_WORKERS = 16

# Output filename suffix for batch encode
BATCH_OUTPUT_SUFFIX = "_encoded"

# ============================================================================
# DATA FILES
# ============================================================================


def get_data_dir() -> Path:
    """Get the data directory path.

    Checks locations in order:
    1. Package data (installed via pip/wheel) using importlib.resources
    2. Development layout (src/stegasoo -> project root/data)
    3. Docker container (/app/data)
    4. Current working directory fallbacks
    """
    # Try package data first (works when installed via pip)
    try:
        pkg_data = importlib.resources.files("soosef.stegasoo.data")
        # Check if the package data directory exists and has our files
        if (pkg_data / "bip39-words.txt").is_file():
            # Return as Path - importlib.resources.files returns a Traversable
            return Path(str(pkg_data))
    except (ModuleNotFoundError, TypeError):
        pass

    # Fallback to file-based locations
    # From src/stegasoo/constants.py:
    #   .parent = src/stegasoo/
    #   .parent.parent = src/
    #   .parent.parent.parent = project root (where data/ lives)
    candidates = [
        Path(__file__).parent / "data",  # Installed package (stegasoo/data/)
        Path(__file__).parent.parent.parent / "data",  # Development: src/stegasoo -> project root
        Path("/app/data"),  # Docker
        Path.cwd() / "data",  # Current directory
        Path.cwd().parent / "data",  # One level up from cwd
        Path.cwd().parent.parent / "data",  # Two levels up from cwd
    ]

    for path in candidates:
        if path.exists():
            return path

    # Default to package data path for clearer error messages
    return Path(__file__).parent / "data"


def get_bip39_words() -> list[str]:
    """Load BIP-39 wordlist."""
    wordlist_path = get_data_dir() / "bip39-words.txt"

    if not wordlist_path.exists():
        raise FileNotFoundError(
            f"BIP-39 wordlist not found at {wordlist_path}. "
            "Please ensure bip39-words.txt is in the data directory."
        )

    with open(wordlist_path) as f:
        return [line.strip() for line in f if line.strip()]


# Lazy-loaded wordlist
_bip39_words: list[str] | None = None


def get_wordlist() -> list[str]:
    """Get the BIP-39 wordlist (cached)."""
    global _bip39_words
    if _bip39_words is None:
        _bip39_words = get_bip39_words()
    return _bip39_words


# =============================================================================
# STEGANALYSIS (v4.4.0)
# =============================================================================

# Chi-square p-value threshold: HIGH p-value = equalized PoV pairs = suspicious
STEGANALYSIS_CHI_SUSPICIOUS_THRESHOLD = 0.95  # p > 0.95 → pairs suspiciously equalized

# RS embedding rate thresholds (primary metric): higher = more likely embedded
STEGANALYSIS_RS_HIGH_THRESHOLD = 0.3  # > 30% estimated embedding → high risk
STEGANALYSIS_RS_MEDIUM_THRESHOLD = 0.1  # > 10% estimated embedding → medium risk

# =============================================================================
# DCT STEGANOGRAPHY (v3.0+)
# =============================================================================

# Embedding modes
EMBED_MODE_LSB = "lsb"  # Spatial LSB embedding (default, original mode)
EMBED_MODE_DCT = "dct"  # DCT domain embedding (new in v3.0)
EMBED_MODE_AUTO = "auto"  # Auto-detect on decode

# DCT-specific constants
DCT_MAGIC_HEADER = b"\x89DCT"  # Magic header for DCT mode
DCT_FORMAT_VERSION = 1
DCT_STEP_SIZE = 8  # QIM quantization step

# Recovery key obfuscation - FIXED value for admin recovery QR codes
# SHA256("\x89ST3\x89DCT") - hardcoded so it never changes even if headers are added
# Used to XOR recovery keys in QR codes so they scan as gibberish
RECOVERY_OBFUSCATION_KEY = bytes.fromhex(
    "d6c70bce27780db942562550e9fe1459" "9dfdb8421f5acc79696b05db4e7afbd2"
)  # 32 bytes

# Valid embedding modes
VALID_EMBED_MODES = {EMBED_MODE_LSB, EMBED_MODE_DCT}

# Capacity estimation constants
LSB_BYTES_PER_PIXEL = 3 / 8  # 3 bits per pixel (RGB, 1 bit per channel) / 8 bits per byte
DCT_BYTES_PER_PIXEL = 0.125  # Approximate for DCT mode (varies by implementation)


def detect_stego_mode(encrypted_data: bytes) -> str:
    """
    Detect embedding mode from encrypted payload header.

    Args:
        encrypted_data: First few bytes of extracted payload

    Returns:
        'lsb' or 'dct' or 'unknown'
    """
    if len(encrypted_data) < 4:
        return "unknown"

    header = encrypted_data[:4]

    if header == b"\x89ST3":
        return EMBED_MODE_LSB
    elif header == b"\x89DCT":
        return EMBED_MODE_DCT
    else:
        return "unknown"


# =============================================================================
# FEATURE TOGGLES (v4.3.1)
# =============================================================================
# Environment variables to enable/disable optional feature families.
# Values: "auto" (default — detect dependencies), "1"/"true" (force on),
#         "0"/"false" (force off even if deps are installed).
# Pi builds or minimal installs can set STEGASOO_AUDIO=0 to stay image-only.

import os as _os


def _parse_feature_toggle(env_var: str, default: str = "auto") -> str | bool:
    """Parse a feature toggle env var. Returns 'auto', True, or False."""
    val = _os.environ.get(env_var, default).strip().lower()
    if val in ("1", "true", "yes", "on"):
        return True
    if val in ("0", "false", "no", "off"):
        return False
    return "auto"


def _check_audio_deps() -> bool:
    """Check if audio dependencies (soundfile, numpy) are importable."""
    try:
        import numpy  # noqa: F401
        import soundfile  # noqa: F401

        return True
    except ImportError:
        return False


def _check_video_deps() -> bool:
    """Check if video dependencies (ffmpeg binary + audio deps) are available."""
    import shutil

    if not _check_audio_deps():
        return False
    return shutil.which("ffmpeg") is not None


def _resolve_feature(toggle: str | bool, dep_check: callable) -> bool:
    """Resolve a feature toggle to a final bool."""
    if toggle is True:
        if not dep_check():
            raise ImportError(
                f"Feature force-enabled but required dependencies are missing. "
                f"Install the relevant extras (e.g. pip install stegasoo[audio])."
            )
        return True
    if toggle is False:
        return False
    # auto
    return dep_check()


_audio_toggle = _parse_feature_toggle("STEGASOO_AUDIO")
_video_toggle = _parse_feature_toggle("STEGASOO_VIDEO")

AUDIO_ENABLED: bool = _resolve_feature(_audio_toggle, _check_audio_deps)
VIDEO_ENABLED: bool = _resolve_feature(_video_toggle, _check_video_deps)


# =============================================================================
# AUDIO STEGANOGRAPHY (v4.3.0)
# =============================================================================

# Audio embedding modes
EMBED_MODE_AUDIO_LSB = "audio_lsb"
EMBED_MODE_AUDIO_SPREAD = "audio_spread"
EMBED_MODE_AUDIO_AUTO = "audio_auto"
VALID_AUDIO_EMBED_MODES = {EMBED_MODE_AUDIO_LSB, EMBED_MODE_AUDIO_SPREAD}

# Audio magic bytes (for format detection in stego audio)
AUDIO_MAGIC_LSB = b"AUDL"
AUDIO_MAGIC_SPREAD = b"AUDS"

# Audio input limits
MAX_AUDIO_DURATION = 600  # 10 minutes
MAX_AUDIO_FILE_SIZE = 100 * 1024 * 1024  # 100 MB
MIN_AUDIO_SAMPLE_RATE = 8000  # G.729 level
MAX_AUDIO_SAMPLE_RATE = 192000  # Studio quality
ALLOWED_AUDIO_EXTENSIONS = {"wav", "flac", "mp3", "ogg", "opus", "aac", "m4a", "wma"}

# Spread spectrum parameters
AUDIO_SS_CHIP_LENGTH = 1024  # Samples per chip (spreading factor) — legacy/default
AUDIO_SS_AMPLITUDE = 0.05  # Per-sample embedding strength (~-26dB, masked by audio)
AUDIO_SS_RS_NSYM = 32  # Reed-Solomon parity symbols

# Spread spectrum v2: per-channel hybrid embedding (v4.4.0)
AUDIO_SS_HEADER_VERSION = 2  # v2 header format identifier

# Chip tier system — trade capacity for robustness
AUDIO_SS_CHIP_TIER_LOSSLESS = 0  # 256 chips — lossless carriers (FLAC/WAV/ALAC)
AUDIO_SS_CHIP_TIER_HIGH_LOSSY = 1  # 512 chips — high-rate lossy (AAC 256k+)
AUDIO_SS_CHIP_TIER_LOW_LOSSY = 2  # 1024 chips — low-rate lossy (AAC 128k, Opus)
AUDIO_SS_DEFAULT_CHIP_TIER = 2  # Most robust, backward compatible
AUDIO_SS_CHIP_LENGTHS = {0: 256, 1: 512, 2: 1024}

# Chip tier name mapping (for CLI/UI)
AUDIO_SS_CHIP_TIER_NAMES = {
    "lossless": AUDIO_SS_CHIP_TIER_LOSSLESS,
    "high": AUDIO_SS_CHIP_TIER_HIGH_LOSSY,
    "low": AUDIO_SS_CHIP_TIER_LOW_LOSSY,
}

# LFE channel skipping — LFE is bandlimited to ~120Hz, terrible carrier
AUDIO_LFE_CHANNEL_INDEX = 3  # Standard WAV/WAVEFORMATEXTENSIBLE ordering
AUDIO_LFE_MIN_CHANNELS = 6  # Only skip LFE for 5.1+ layouts

# Echo hiding parameters
AUDIO_ECHO_DELAY_0 = 50  # Echo delay for bit 0 (samples at 44.1kHz ~ 1.1ms)
AUDIO_ECHO_DELAY_1 = 100  # Echo delay for bit 1 (samples at 44.1kHz ~ 2.3ms)
AUDIO_ECHO_AMPLITUDE = 0.3  # Echo strength (relative to original)
AUDIO_ECHO_WINDOW_SIZE = 8192  # Window size for echo embedding


# =============================================================================
# VIDEO STEGANOGRAPHY (v4.4.0)
# =============================================================================

# Video embedding modes
EMBED_MODE_VIDEO_LSB = "video_lsb"
EMBED_MODE_VIDEO_AUTO = "video_auto"
VALID_VIDEO_EMBED_MODES = {EMBED_MODE_VIDEO_LSB}

# Video magic bytes (for format detection in stego video)
VIDEO_MAGIC_LSB = b"VIDL"

# Video input limits
MAX_VIDEO_FILE_SIZE = 4 * 1024 * 1024 * 1024  # 4 GB
MAX_VIDEO_DURATION = 3600  # 1 hour in seconds
MIN_VIDEO_RESOLUTION = (64, 64)
MAX_VIDEO_RESOLUTION = (7680, 4320)  # 8K UHD
ALLOWED_VIDEO_EXTENSIONS = {"mp4", "mkv", "webm", "avi", "mov"}

# Video output settings
VIDEO_OUTPUT_CODEC = "ffv1"  # FFV1 lossless codec
VIDEO_OUTPUT_CONTAINER = "mkv"  # MKV container for FFV1
