"""
Stegasoo Decode Module (v4.0.0)

High-level decoding functions for extracting messages and files from images.

Changes in v4.0.0:
- Added channel_key parameter for deployment/group isolation
- Improved error messages for channel key mismatches
"""

import json
from pathlib import Path

from .constants import EMBED_MODE_AUTO
from .crypto import decrypt_message
from .debug import debug
from .exceptions import DecryptionError, ExtractionError
from .models import DecodeResult
from .steganography import extract_from_image
from .validation import (
    require_security_factors,
    require_valid_image,
    require_valid_pin,
    require_valid_rsa_key,
)


def _write_progress(progress_file: str | None, current: int, total: int, phase: str) -> None:
    """Write progress to file for UI polling."""
    if progress_file is None:
        return
    try:
        with open(progress_file, "w") as f:
            json.dump(
                {
                    "current": current,
                    "total": total,
                    "percent": (current / total * 100) if total > 0 else 0,
                    "phase": phase,
                },
                f,
            )
    except OSError:
        pass


def decode(
    stego_image: bytes,
    reference_photo: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    embed_mode: str = EMBED_MODE_AUTO,
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
    platform: str | None = None,
) -> DecodeResult:
    """
    Decode a message or file from a stego image.

    Args:
        stego_image: Stego image bytes
        reference_photo: Shared reference photo bytes
        passphrase: Shared passphrase used during encoding
        pin: Optional static PIN (if used during encoding)
        rsa_key_data: Optional RSA key bytes (if used during encoding)
        rsa_password: Optional RSA key password
        embed_mode: 'auto' (default), 'lsb', or 'dct'
        progress_file: Optional path to write progress JSON for UI polling
        channel_key: Channel key for deployment/group isolation:
            - None or "auto": Use server's configured key
            - str: Use this specific channel key
            - "" or False: No channel key (public mode)

    Returns:
        DecodeResult with message or file data

    Example:
        >>> result = decode(
        ...     stego_image=stego_bytes,
        ...     reference_photo=ref_bytes,
        ...     passphrase="apple forest thunder mountain",
        ...     pin="123456"
        ... )
        >>> if result.is_text:
        ...     print(result.message)
        ... else:
        ...     with open(result.filename, 'wb') as f:
        ...         f.write(result.file_data)

    Example with explicit channel key:
        >>> result = decode(
        ...     stego_image=stego_bytes,
        ...     reference_photo=ref_bytes,
        ...     passphrase="apple forest thunder mountain",
        ...     pin="123456",
        ...     channel_key="ABCD-1234-EFGH-5678-IJKL-9012-MNOP-3456"
        ... )
    """
    debug.print(
        f"decode: passphrase length={len(passphrase.split())} words, "
        f"mode={embed_mode}, "
        f"channel_key={'explicit' if isinstance(channel_key, str) and channel_key else 'auto' if channel_key is None else 'none'}"
    )

    # Validate inputs
    require_valid_image(stego_image, "Stego image")
    require_valid_image(reference_photo, "Reference photo")
    require_security_factors(pin, rsa_key_data)

    if pin:
        require_valid_pin(pin)
    if rsa_key_data:
        require_valid_rsa_key(rsa_key_data, rsa_password)

    # Progress: starting key derivation (Argon2 - slow on Pi)
    _write_progress(progress_file, 20, 100, "initializing")

    # Derive pixel/coefficient selection key (with channel key)
    from .crypto import derive_pixel_key

    pixel_key = derive_pixel_key(reference_photo, passphrase, pin, rsa_key_data, channel_key)

    # Progress: key derivation done, starting extraction
    _write_progress(progress_file, 25, 100, "extracting")

    # Resolve platform preset for DCT extraction
    extract_kwargs = {}
    if platform:
        from .platform_presets import get_preset

        preset = get_preset(platform)
        extract_kwargs["quant_step"] = preset.quant_step

    # Extract encrypted data
    encrypted = extract_from_image(
        stego_image,
        pixel_key,
        embed_mode=embed_mode,
        progress_file=progress_file,
        **extract_kwargs,
    )

    if not encrypted:
        debug.print("No data extracted from image")
        raise ExtractionError("Could not extract data. Check your credentials and image.")

    debug.print(f"Extracted {len(encrypted)} bytes from image")

    # Decrypt (with channel key)
    result = decrypt_message(encrypted, reference_photo, passphrase, pin, rsa_key_data, channel_key)

    debug.print(f"Decryption successful: {result.payload_type}")
    return result


def decode_file(
    stego_image: bytes,
    reference_photo: bytes,
    passphrase: str,
    output_path: Path | None = None,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    embed_mode: str = EMBED_MODE_AUTO,
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
) -> Path:
    """
    Decode a file from a stego image and save it.

    Args:
        stego_image: Stego image bytes
        reference_photo: Shared reference photo bytes
        passphrase: Shared passphrase
        output_path: Optional output path (defaults to original filename)
        pin: Optional static PIN
        rsa_key_data: Optional RSA key bytes
        rsa_password: Optional RSA key password
        embed_mode: 'auto', 'lsb', or 'dct'
        channel_key: Channel key parameter (see decode())
        progress_file: Optional path to write progress JSON for UI polling

    Returns:
        Path where file was saved

    Raises:
        DecryptionError: If payload is text, not a file
    """
    result = decode(
        stego_image,
        reference_photo,
        passphrase,
        pin,
        rsa_key_data,
        rsa_password,
        embed_mode,
        channel_key,
        progress_file,
    )

    if not result.is_file:
        raise DecryptionError("Payload is a text message, not a file")

    if output_path is None:
        output_path = Path(result.filename or "extracted_file")
    else:
        output_path = Path(output_path)
        if output_path.is_dir():
            output_path = output_path / (result.filename or "extracted_file")

    # Write file
    output_path.write_bytes(result.file_data or b"")

    debug.print(f"File saved to: {output_path}")
    return output_path


def decode_text(
    stego_image: bytes,
    reference_photo: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    embed_mode: str = EMBED_MODE_AUTO,
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
) -> str:
    """
    Decode a text message from a stego image.

    Convenience function that returns just the message string.

    Args:
        stego_image: Stego image bytes
        reference_photo: Shared reference photo bytes
        passphrase: Shared passphrase
        pin: Optional static PIN
        rsa_key_data: Optional RSA key bytes
        rsa_password: Optional RSA key password
        embed_mode: 'auto', 'lsb', or 'dct'
        channel_key: Channel key parameter (see decode())
        progress_file: Optional path to write progress JSON for UI polling

    Returns:
        Decoded message string

    Raises:
        DecryptionError: If payload is a file, not text
    """
    result = decode(
        stego_image,
        reference_photo,
        passphrase,
        pin,
        rsa_key_data,
        rsa_password,
        embed_mode,
        channel_key,
        progress_file,
    )

    if result.is_file:
        # Try to decode as text
        if result.file_data:
            try:
                return result.file_data.decode("utf-8")
            except UnicodeDecodeError:
                raise DecryptionError(
                    f"Payload is a binary file ({result.filename or 'unnamed'}), not text"
                )
        return ""

    return result.message or ""


def decode_audio(
    stego_audio: bytes,
    reference_photo: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    embed_mode: str = "audio_auto",
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
) -> DecodeResult:
    """
    Decode a message or file from stego audio.

    Args:
        stego_audio: Stego audio bytes
        reference_photo: Shared reference photo bytes
        passphrase: Shared passphrase
        pin: Optional static PIN
        rsa_key_data: Optional RSA key bytes
        rsa_password: Optional RSA key password
        embed_mode: 'audio_auto', 'audio_lsb', or 'audio_spread'
        channel_key: Channel key for deployment/group isolation
        progress_file: Optional path to write progress JSON

    Returns:
        DecodeResult with message or file data
    """
    from .constants import (
        AUDIO_ENABLED,
        EMBED_MODE_AUDIO_AUTO,
        EMBED_MODE_AUDIO_LSB,
        EMBED_MODE_AUDIO_SPREAD,
    )

    if not AUDIO_ENABLED:
        raise ExtractionError(
            "Audio support is disabled. Install audio extras (pip install stegasoo[audio]) "
            "or set STEGASOO_AUDIO=1 to force enable."
        )

    from .audio_utils import detect_audio_format, transcode_to_wav

    debug.print(
        f"decode_audio: mode={embed_mode}, " f"passphrase length={len(passphrase.split())} words"
    )

    # Validate inputs
    require_valid_image(reference_photo, "Reference photo")
    require_security_factors(pin, rsa_key_data)

    if pin:
        require_valid_pin(pin)
    if rsa_key_data:
        require_valid_rsa_key(rsa_key_data, rsa_password)

    # Detect format and transcode to WAV for processing
    audio_format = detect_audio_format(stego_audio)
    debug.print(f"Detected audio format: {audio_format}")

    wav_audio = stego_audio
    if audio_format != "wav":
        debug.print(f"Transcoding {audio_format} to WAV for extraction")
        wav_audio = transcode_to_wav(stego_audio)

    _write_progress(progress_file, 20, 100, "initializing")

    # Derive sample selection key
    from .crypto import derive_pixel_key

    pixel_key = derive_pixel_key(reference_photo, passphrase, pin, rsa_key_data, channel_key)

    _write_progress(progress_file, 25, 100, "extracting")

    encrypted = None

    if embed_mode == EMBED_MODE_AUDIO_AUTO:
        # Try modes in order: spread spectrum -> LSB
        try:
            from .spread_steganography import extract_from_audio_spread

            encrypted = extract_from_audio_spread(wav_audio, pixel_key)
            if encrypted:
                debug.print("Auto-detect: spread spectrum extraction succeeded")
        except (ImportError, Exception):
            pass

        if not encrypted:
            from .audio_steganography import extract_from_audio_lsb

            encrypted = extract_from_audio_lsb(wav_audio, pixel_key)
            if encrypted:
                debug.print("Auto-detect: LSB extraction succeeded")

    elif embed_mode == EMBED_MODE_AUDIO_LSB:
        from .audio_steganography import extract_from_audio_lsb

        encrypted = extract_from_audio_lsb(wav_audio, pixel_key, progress_file=progress_file)

    elif embed_mode == EMBED_MODE_AUDIO_SPREAD:
        from .spread_steganography import extract_from_audio_spread

        encrypted = extract_from_audio_spread(wav_audio, pixel_key, progress_file=progress_file)
    else:
        raise ValueError(f"Invalid audio embed mode: {embed_mode}")

    if not encrypted:
        debug.print("No data extracted from audio")
        raise ExtractionError("Could not extract data from audio. Check your credentials.")

    debug.print(f"Extracted {len(encrypted)} bytes from audio")

    # Decrypt
    result = decrypt_message(encrypted, reference_photo, passphrase, pin, rsa_key_data, channel_key)

    debug.print(f"Decryption successful: {result.payload_type}")
    return result


def decode_video(
    stego_video: bytes,
    reference_photo: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    embed_mode: str = "video_auto",
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
) -> DecodeResult:
    """
    Decode a message or file from stego video.

    Extracts data from I-frames (keyframes) using LSB steganography.

    Args:
        stego_video: Stego video bytes
        reference_photo: Shared reference photo bytes
        passphrase: Shared passphrase
        pin: Optional static PIN
        rsa_key_data: Optional RSA key bytes
        rsa_password: Optional RSA key password
        embed_mode: 'video_auto' or 'video_lsb'
        channel_key: Channel key for deployment/group isolation
        progress_file: Optional path to write progress JSON

    Returns:
        DecodeResult with message or file data
    """
    from .constants import (
        EMBED_MODE_VIDEO_AUTO,
        EMBED_MODE_VIDEO_LSB,
        VIDEO_ENABLED,
    )

    if not VIDEO_ENABLED:
        raise ExtractionError(
            "Video support is disabled. Install video extras and ffmpeg, "
            "or set STEGASOO_VIDEO=1 to force enable."
        )

    from .video_utils import detect_video_format

    debug.print(
        f"decode_video: mode={embed_mode}, " f"passphrase length={len(passphrase.split())} words"
    )

    # Validate inputs
    require_valid_image(reference_photo, "Reference photo")
    require_security_factors(pin, rsa_key_data)

    if pin:
        require_valid_pin(pin)
    if rsa_key_data:
        require_valid_rsa_key(rsa_key_data, rsa_password)

    # Detect format
    video_format = detect_video_format(stego_video)
    debug.print(f"Detected video format: {video_format}")

    if video_format == "unknown":
        raise ExtractionError("Could not detect video format.")

    _write_progress(progress_file, 20, 100, "initializing")

    # Derive pixel/frame selection key
    from .crypto import derive_pixel_key

    pixel_key = derive_pixel_key(reference_photo, passphrase, pin, rsa_key_data, channel_key)

    _write_progress(progress_file, 25, 100, "extracting")

    encrypted = None

    if embed_mode == EMBED_MODE_VIDEO_AUTO or embed_mode == EMBED_MODE_VIDEO_LSB:
        from .video_steganography import extract_from_video_lsb

        encrypted = extract_from_video_lsb(stego_video, pixel_key, progress_file=progress_file)
        if encrypted:
            debug.print("Video LSB extraction succeeded")
    else:
        raise ValueError(f"Invalid video embed mode: {embed_mode}")

    if not encrypted:
        debug.print("No data extracted from video")
        raise ExtractionError("Could not extract data from video. Check your credentials.")

    debug.print(f"Extracted {len(encrypted)} bytes from video")

    # Decrypt
    result = decrypt_message(encrypted, reference_photo, passphrase, pin, rsa_key_data, channel_key)

    debug.print(f"Decryption successful: {result.payload_type}")
    return result
