"""
Stegasoo Encode Module (v4.0.0)

High-level encoding functions for hiding messages and files in images.

Changes in v4.0.0:
- Added channel_key parameter for deployment/group isolation

Changes in v4.3.0:
- Added encode_audio() for audio steganography

Changes in v4.4.0:
- Added encode_video() for video steganography
"""

from __future__ import annotations

from pathlib import Path
from typing import TYPE_CHECKING

from .constants import EMBED_MODE_LSB
from .crypto import derive_pixel_key, encrypt_message
from .debug import debug
from .exceptions import AudioError, VideoError
from .models import EncodeResult, FilePayload
from .steganography import embed_in_image
from .utils import generate_filename
from .validation import (
    require_security_factors,
    require_valid_image,
    require_valid_payload,
    require_valid_pin,
    require_valid_rsa_key,
)

if TYPE_CHECKING:
    from .models import AudioEmbedStats, VideoEmbedStats


def encode(
    message: str | bytes | FilePayload,
    reference_photo: bytes,
    carrier_image: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    output_format: str | None = None,
    embed_mode: str = EMBED_MODE_LSB,
    dct_output_format: str = "png",
    dct_color_mode: str = "color",
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
    platform: str | None = None,
    transport: str | None = None,
    strip_metadata: bool = True,
) -> EncodeResult:
    """
    Encode a message or file into an image.

    Args:
        message: Text message, raw bytes, or FilePayload to hide
        reference_photo: Shared reference photo bytes
        carrier_image: Carrier image bytes
        passphrase: Shared passphrase (recommend 4+ words)
        pin: Optional static PIN
        rsa_key_data: Optional RSA private key PEM bytes
        rsa_password: Optional password for encrypted RSA key
        output_format: Force output format ('PNG', 'BMP') - LSB mode only
        embed_mode: 'lsb' (default) or 'dct'
        dct_output_format: For DCT mode - 'png' or 'jpeg'
        dct_color_mode: For DCT mode - 'grayscale' or 'color'
        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:
        EncodeResult with stego image and metadata

    Example:
        >>> result = encode(
        ...     message="Secret message",
        ...     reference_photo=ref_bytes,
        ...     carrier_image=carrier_bytes,
        ...     passphrase="apple forest thunder mountain",
        ...     pin="123456"
        ... )
        >>> with open('stego.png', 'wb') as f:
        ...     f.write(result.stego_image)

    Example with explicit channel key:
        >>> result = encode(
        ...     message="Secret message",
        ...     reference_photo=ref_bytes,
        ...     carrier_image=carrier_bytes,
        ...     passphrase="apple forest thunder mountain",
        ...     pin="123456",
        ...     channel_key="ABCD-1234-EFGH-5678-IJKL-9012-MNOP-3456"
        ... )
    """
    debug.print(
        f"encode: passphrase length={len(passphrase.split())} words, "
        f"pin={'set' if pin else 'none'}, 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_payload(message)
    require_valid_image(reference_photo, "Reference photo")
    require_valid_image(carrier_image, "Carrier image")
    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)

    # Strip EXIF/metadata from carrier to prevent source information leakage.
    # Pillow re-save without copying info/exif fields removes all metadata.
    if strip_metadata:
        import io

        from PIL import Image

        img = Image.open(io.BytesIO(carrier_image))
        clean = io.BytesIO()
        img.save(clean, format=img.format or "PNG")
        carrier_image = clean.getvalue()
        debug.print("Stripped metadata from carrier image")

    # Transport-aware encoding: force DCT mode and resize carrier for lossy channels
    if transport:
        transport = transport.lower()
        lossy_transports = {"whatsapp", "signal", "telegram", "discord"}
        if transport in lossy_transports:
            from .platform_presets import get_preset

            preset = get_preset(transport)
            embed_mode = EMBED_MODE_DCT
            dct_output_format = "jpeg"
            platform = transport
            debug.print(f"Transport '{transport}': forcing DCT/JPEG mode, max {preset.max_dimension}px")

            # Pre-resize carrier to platform max dimension to prevent
            # post-encode resize destroying the payload
            import io

            from PIL import Image

            img = Image.open(io.BytesIO(carrier_image))
            w, h = img.size
            if max(w, h) > preset.max_dimension:
                scale = preset.max_dimension / max(w, h)
                new_size = (int(w * scale), int(h * scale))
                img = img.resize(new_size, Image.LANCZOS)
                buf = io.BytesIO()
                img.save(buf, format="JPEG", quality=95)
                carrier_image = buf.getvalue()
                debug.print(f"Resized carrier from {w}x{h} to {new_size[0]}x{new_size[1]}")
        elif transport == "email":
            # Email attachments are not recompressed; LSB is fine
            debug.print("Transport 'email': using default mode (attachments preserved)")
        elif transport == "direct":
            debug.print("Transport 'direct': using default mode (no recompression)")
        else:
            from .platform_presets import PLATFORMS

            raise ValueError(
                f"Unknown transport '{transport}'. "
                f"Available: {', '.join(sorted(lossy_transports | {'email', 'direct'}))}"
            )

    # Encrypt message (with channel key)
    encrypted = encrypt_message(
        message, reference_photo, passphrase, pin, rsa_key_data, channel_key
    )

    debug.print(f"Encrypted payload: {len(encrypted)} bytes")

    # Derive pixel/coefficient selection key (with channel key)
    pixel_key = derive_pixel_key(reference_photo, passphrase, pin, rsa_key_data, channel_key)

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

        preset = get_preset(platform)
        platform_kwargs = {
            "quant_step": preset.quant_step,
            "max_dimension": preset.max_dimension,
            "jpeg_quality": preset.jpeg_quality,
        }

    # Embed in image
    stego_data, stats, extension = embed_in_image(
        encrypted,
        carrier_image,
        pixel_key,
        output_format=output_format,
        embed_mode=embed_mode,
        dct_output_format=dct_output_format,
        dct_color_mode=dct_color_mode,
        progress_file=progress_file,
        **platform_kwargs,
    )

    # Generate filename
    filename = generate_filename(extension=extension)

    # Create result
    if hasattr(stats, "pixels_modified"):
        # LSB mode stats
        return EncodeResult(
            stego_image=stego_data,
            filename=filename,
            pixels_modified=stats.pixels_modified,
            total_pixels=stats.total_pixels,
            capacity_used=stats.capacity_used,
            date_used=None,  # No longer used in v3.2.0+
        )
    else:
        # DCT mode stats
        return EncodeResult(
            stego_image=stego_data,
            filename=filename,
            pixels_modified=stats.blocks_used * 64,
            total_pixels=stats.blocks_available * 64,
            capacity_used=stats.usage_percent / 100.0,
            date_used=None,
        )


def encode_file(
    filepath: str | Path,
    reference_photo: bytes,
    carrier_image: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    output_format: str | None = None,
    filename_override: str | None = None,
    embed_mode: str = EMBED_MODE_LSB,
    dct_output_format: str = "png",
    dct_color_mode: str = "color",
    channel_key: str | bool | None = None,
) -> EncodeResult:
    """
    Encode a file into an image.

    Convenience wrapper that loads a file and encodes it.

    Args:
        filepath: Path to file to embed
        reference_photo: Shared reference photo bytes
        carrier_image: Carrier image bytes
        passphrase: Shared passphrase
        pin: Optional static PIN
        rsa_key_data: Optional RSA key bytes
        rsa_password: Optional RSA key password
        output_format: Force output format - LSB only
        filename_override: Override stored filename
        embed_mode: 'lsb' or 'dct'
        dct_output_format: 'png' or 'jpeg'
        dct_color_mode: 'grayscale' or 'color'
        channel_key: Channel key parameter (see encode())

    Returns:
        EncodeResult
    """
    payload = FilePayload.from_file(str(filepath), filename_override)

    return encode(
        message=payload,
        reference_photo=reference_photo,
        carrier_image=carrier_image,
        passphrase=passphrase,
        pin=pin,
        rsa_key_data=rsa_key_data,
        rsa_password=rsa_password,
        output_format=output_format,
        embed_mode=embed_mode,
        dct_output_format=dct_output_format,
        dct_color_mode=dct_color_mode,
        channel_key=channel_key,
    )


def encode_bytes(
    data: bytes,
    filename: str,
    reference_photo: bytes,
    carrier_image: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    output_format: str | None = None,
    mime_type: str | None = None,
    embed_mode: str = EMBED_MODE_LSB,
    dct_output_format: str = "png",
    dct_color_mode: str = "color",
    channel_key: str | bool | None = None,
) -> EncodeResult:
    """
    Encode raw bytes with metadata into an image.

    Args:
        data: Raw bytes to embed
        filename: Filename to associate with data
        reference_photo: Shared reference photo bytes
        carrier_image: Carrier image bytes
        passphrase: Shared passphrase
        pin: Optional static PIN
        rsa_key_data: Optional RSA key bytes
        rsa_password: Optional RSA key password
        output_format: Force output format - LSB only
        mime_type: MIME type of data
        embed_mode: 'lsb' or 'dct'
        dct_output_format: 'png' or 'jpeg'
        dct_color_mode: 'grayscale' or 'color'
        channel_key: Channel key parameter (see encode())

    Returns:
        EncodeResult
    """
    payload = FilePayload(data=data, filename=filename, mime_type=mime_type)

    return encode(
        message=payload,
        reference_photo=reference_photo,
        carrier_image=carrier_image,
        passphrase=passphrase,
        pin=pin,
        rsa_key_data=rsa_key_data,
        rsa_password=rsa_password,
        output_format=output_format,
        embed_mode=embed_mode,
        dct_output_format=dct_output_format,
        dct_color_mode=dct_color_mode,
        channel_key=channel_key,
    )


def encode_audio(
    message: str | bytes | FilePayload,
    reference_photo: bytes,
    carrier_audio: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    embed_mode: str = "audio_lsb",
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
    chip_tier: int | None = None,
) -> tuple[bytes, AudioEmbedStats]:
    """
    Encode a message or file into an audio carrier.

    Args:
        message: Text message, raw bytes, or FilePayload to hide
        reference_photo: Shared reference photo bytes
        carrier_audio: Carrier audio bytes (WAV, FLAC, MP3, etc.)
        passphrase: Shared passphrase
        pin: Optional static PIN
        rsa_key_data: Optional RSA private key PEM bytes
        rsa_password: Optional password for encrypted RSA key
        embed_mode: 'audio_lsb' or 'audio_spread'
        channel_key: Channel key for deployment/group isolation
        progress_file: Optional path to write progress JSON
        chip_tier: Spread spectrum chip tier (0=lossless, 1=high_lossy, 2=low_lossy).
                   Only used for audio_spread mode. Default None → uses constant default.

    Returns:
        Tuple of (stego audio bytes, AudioEmbedStats)
    """
    from .constants import AUDIO_ENABLED, EMBED_MODE_AUDIO_LSB, EMBED_MODE_AUDIO_SPREAD

    if not AUDIO_ENABLED:
        raise AudioError(
            "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"encode_audio: mode={embed_mode}, "
        f"passphrase length={len(passphrase.split())} words, "
        f"pin={'set' if pin else 'none'}"
    )

    # Validate inputs
    require_valid_payload(message)
    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 audio format and transcode to WAV if needed
    audio_format = detect_audio_format(carrier_audio)
    debug.print(f"Detected audio format: {audio_format}")

    if audio_format not in ("wav", "flac"):
        debug.print(f"Transcoding {audio_format} to WAV for embedding")
        carrier_audio = transcode_to_wav(carrier_audio)

    # Encrypt message
    encrypted = encrypt_message(
        message, reference_photo, passphrase, pin, rsa_key_data, channel_key
    )
    debug.print(f"Encrypted payload: {len(encrypted)} bytes")

    # Derive sample selection key
    pixel_key = derive_pixel_key(reference_photo, passphrase, pin, rsa_key_data, channel_key)

    # Embed based on mode
    if embed_mode == EMBED_MODE_AUDIO_LSB:
        from .audio_steganography import embed_in_audio_lsb

        stego_audio, stats = embed_in_audio_lsb(
            encrypted, carrier_audio, pixel_key, progress_file=progress_file
        )
    elif embed_mode == EMBED_MODE_AUDIO_SPREAD:
        from .constants import AUDIO_SS_DEFAULT_CHIP_TIER
        from .spread_steganography import embed_in_audio_spread

        tier = chip_tier if chip_tier is not None else AUDIO_SS_DEFAULT_CHIP_TIER
        stego_audio, stats = embed_in_audio_spread(
            encrypted, carrier_audio, pixel_key, chip_tier=tier, progress_file=progress_file
        )
    else:
        raise ValueError(f"Invalid audio embed mode: {embed_mode}")

    return stego_audio, stats


def encode_video(
    message: str | bytes | FilePayload,
    reference_photo: bytes,
    carrier_video: bytes,
    passphrase: str,
    pin: str = "",
    rsa_key_data: bytes | None = None,
    rsa_password: str | None = None,
    embed_mode: str = "video_lsb",
    channel_key: str | bool | None = None,
    progress_file: str | None = None,
) -> tuple[bytes, VideoEmbedStats]:
    """
    Encode a message or file into a video carrier.

    Embeds data across I-frames (keyframes) using LSB steganography.
    Output is an MKV container with FFV1 lossless codec to preserve
    the embedded data perfectly.

    Args:
        message: Text message, raw bytes, or FilePayload to hide
        reference_photo: Shared reference photo bytes
        carrier_video: Carrier video bytes (MP4, MKV, WebM, AVI, MOV)
        passphrase: Shared passphrase
        pin: Optional static PIN
        rsa_key_data: Optional RSA private key PEM bytes
        rsa_password: Optional password for encrypted RSA key
        embed_mode: 'video_lsb' (currently the only option)
        channel_key: Channel key for deployment/group isolation
        progress_file: Optional path to write progress JSON

    Returns:
        Tuple of (stego video bytes, VideoEmbedStats)

    Note:
        The output video will be in MKV format with FFV1 lossless codec,
        regardless of the input format. This is necessary to preserve
        the embedded data without lossy compression artifacts.
    """
    from .constants import EMBED_MODE_VIDEO_LSB, VIDEO_ENABLED

    if not VIDEO_ENABLED:
        raise VideoError(
            "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"encode_video: mode={embed_mode}, "
        f"passphrase length={len(passphrase.split())} words, "
        f"pin={'set' if pin else 'none'}"
    )

    # Validate inputs
    require_valid_payload(message)
    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 video format
    video_format = detect_video_format(carrier_video)
    debug.print(f"Detected video format: {video_format}")

    if video_format == "unknown":
        raise VideoError("Could not detect video format. Supported: MP4, MKV, WebM, AVI, MOV.")

    # Encrypt message
    encrypted = encrypt_message(
        message, reference_photo, passphrase, pin, rsa_key_data, channel_key
    )
    debug.print(f"Encrypted payload: {len(encrypted)} bytes")

    # Derive pixel/frame selection key
    pixel_key = derive_pixel_key(reference_photo, passphrase, pin, rsa_key_data, channel_key)

    # Embed based on mode
    if embed_mode == EMBED_MODE_VIDEO_LSB:
        from .video_steganography import embed_in_video_lsb

        stego_video, stats = embed_in_video_lsb(
            encrypted, carrier_video, pixel_key, progress_file=progress_file
        )
    else:
        raise ValueError(f"Invalid video embed mode: {embed_mode}")

    return stego_video, stats
