"""CLI entry point for the zmqcam application.

Provides commands to run the camera publisher and subscriber.
"""

import os

import click

from pogucam.publisher import run_publisher
from pogucam.subscriber import run_subscriber
from pogucam.napari_viewer import process_and_view
from pogucam.image_saving import convert_fits_to_image, convert_fits_to_mp4


def find_auto_video_source() -> int:
    """Find the first available video device that supports YUYV format.

    Scans /dev/video0 through /dev/video9 and returns the index of the
    first device that exists and supports YUYV (YUYV 4:2:2) format.

    Returns:
        The camera index (0-9) of the first suitable device.

    Raises:
        RuntimeError: If no suitable video device is found.
    """
    import cv2

    for idx in range(10):
        device_path = f"/dev/video{idx}"

        # Check if device exists
        if not os.path.exists(device_path):
            continue

        # Try to open the camera with V4L2 backend
        cap = cv2.VideoCapture(idx, cv2.CAP_V4L2)
        if not cap.isOpened():
            cap.release()
            continue

        # Check if YUYV format is supported
        # Get current format and check if it's YUYV or can be set to YUYV
        current_fourcc = int(cap.get(cv2.CAP_PROP_FOURCC))
        current_format = "".join(
            [chr((current_fourcc >> 8 * i) & 0xFF) for i in range(4)]
        )

        # Try to set YUYV format
        yuyv_fourcc = cv2.VideoWriter_fourcc("Y", "U", "Y", "V")
        cap.set(cv2.CAP_PROP_FOURCC, yuyv_fourcc)

        # Verify the format was set
        actual_fourcc = int(cap.get(cv2.CAP_PROP_FOURCC))
        actual_format = "".join(
            [chr((actual_fourcc >> 8 * i) & 0xFF) for i in range(4)]
        )

        cap.release()

        # Check if YUYV was successfully set (or was already YUYV)
        if actual_format.upper() == "YUYV":
            return idx

    raise RuntimeError(
        "No suitable video device found. "
        "Checked /dev/video0 through /dev/video9 for YUYV support."
    )


@click.group()
def main() -> None:
    """ZMQ Camera Streaming - Stream webcam video via ZeroMQ."""
    pass


@main.command()
@click.option(
    "--camera",
    "-c",
    default=0,
    type=int,
    help="Camera device index (default: 0)",
)
@click.option(
    "--auto-video-source",
    "-a",
    is_flag=True,
    help="Auto-detect video source: scan /dev/video0-9 and use first with YUYV support",
)
@click.option(
    "--blue-image",
    "-B",
    is_flag=True,
    help="Send synthetic blue image 640x480 YUYV at 1 fps (no camera needed)",
)
@click.option(
    "--bind",
    "-b",
    default="tcp://*:5555",
    help="ZMQ bind address (default: tcp://*:5555)",
)
@click.option(
    "--fps",
    "-f",
    default=5,
    type=int,
    help="Maximum FPS to publish, 0 for unlimited (default: 5)",
)
@click.option(
    "--width",
    "-w",
    default=1920,
    type=int,
    help="Capture width in pixels (default: 1920)",
)
@click.option(
    "--height",
    default=1080,
    type=int,
    help="Capture height in pixels (default: 1080)",
)
@click.option(
    "--format",
    default="YUYV",
    type=click.Choice(["YUYV", "MJPG"], case_sensitive=False),
    help="Capture pixel format (default: YUYV)",
)
@click.option(
    "--compression",
    "-z",
    default=1,
    type=click.IntRange(0, 22),
    help="Zstd compression level 0-22, 0 to disable (default: 1)",
)
def publish(
    camera: int,
    auto_video_source: bool,
    blue_image: bool,
    bind: str,
    fps: int,
    width: int,
    height: int,
    format: str,
    compression: int,
) -> None:
    """Start the camera publisher to stream video frames.

    Captures video from the specified camera in raw YUYV format and
    publishes frames via ZMQ for subscribers to receive.

    Example:
        zmqcam publish --camera 0 --bind tcp://*:5555
        zmqcam publish -a --bind tcp://*:5555
        zmqcam publish --blue-image --bind tcp://*:5555
    """
    # Check for conflicting options
    if blue_image and auto_video_source:
        raise click.ClickException(
            "Cannot use --blue-image and --auto-video-source together"
        )

    # Use synthetic blue image if requested
    use_synthetic = False
    if blue_image:
        print("Using synthetic blue image at 640x480 YUYV, 1 fps")
        use_synthetic = True
    elif auto_video_source:
        try:
            camera = find_auto_video_source()
            print(f"Auto-detected camera: /dev/video{camera}")
        except RuntimeError:
            print("No video device found. Using synthetic blue image at 640x480 YUYV, 1 fps")
            use_synthetic = True

    try:
        if use_synthetic:
            run_publisher(
                camera=-1,
                bind_address=bind,
                fps=1,
                width=640,
                height=480,
                format="YUYV",
                compression=compression,
            )
        else:
            run_publisher(
                camera=camera,
                bind_address=bind,
                fps=fps,
                width=width,
                height=height,
                format=format,
                compression=compression,
            )
    except RuntimeError as e:
        raise click.ClickException(str(e))


N_BUSRT_IMAGES = 10


@main.command()
@click.option(
    "--connect",
    "-c",
    default="tcp://localhost:5555",
    help="ZMQ connect address (default: tcp://localhost:5555)",
)
@click.option(
    "--no-display",
    is_flag=True,
    help="Run without displaying frames (for processing only)",
)
@click.option(
    "--display-width",
    default=640,
    type=int,
    help="Display width in pixels (default: 640)",
)
@click.option(
    "--display-height",
    default=480,
    type=int,
    help="Display height in pixels (default: 480)",
)
@click.option(
    "--fov",
    default=150.0,
    type=float,
    help="Field of view in degrees for barrel correction (default: 150.0)",
)
@click.option(
    "--no-signal-timeout",
    "-t",
    default=5.0,
    type=float,
    help="Timeout in seconds before showing 'no signal' image when publisher stops (default: 5.0)",
)
@click.option(
    "--burst",
    default=N_BUSRT_IMAGES,
    type=int,
    help=f"Number of images to save in a FITS burst sequence (default: {N_BUSRT_IMAGES})",
)
@click.option(
    "--record-duration",
    "-d",
    default=10,
    type=int,
    help="Duration in seconds for time-based recording (default: 10)",
)
@click.option(
    "--burst-mode",
    default="individual",
    type=click.Choice(["individual", "sequence"], case_sensitive=False),
    help="Burst mode: 'individual' for separate FITS files, 'sequence' for one FITS cube (default: individual)",
)
def subscribe(
    connect: str,
    no_display: bool,
    display_width: int,
    display_height: int,
    fov: float,
    no_signal_timeout: float,
    burst: int,
    record_duration: int,
    burst_mode: str,
) -> None:
    """Start the camera subscriber to receive video frames.

    Connects to a ZMQ publisher and displays or processes
    the received video frames.

    Example:
        zmqcam subscribe --connect tcp://localhost:5555
    """
    run_subscriber(
        connect_address=connect,
        show_window=not no_display,
        display_width=display_width,
        display_height=display_height,
        fov_degrees=fov,
        no_signal_timeout=no_signal_timeout,
        max_frames_in_fits=burst,
        record_duration=record_duration,
        burst_mode=burst_mode,
    )


@main.command()
@click.argument("patterns", nargs=-1, required=True)
@click.option(
    "--keep-temp",
    "-k",
    is_flag=True,
    help="Keep the temporary TIFF file after viewing",
)
@click.option(
    "--temp-dir",
    "-t",
    default="/tmp",
    help="Directory for temporary TIFF file (default: /tmp)",
)
def napari(patterns, keep_temp, temp_dir):
    """View FITS files as a stack in napari.

    Combines multiple FITS files into a temporary multi-page TIFF stack
    and opens it in napari for interactive viewing.

    Examples:
        zmqcam napari *.fits
        zmqcam napari image_*.fits
        zmqcam napari --keep-temp *.fits
        zmqcam napari --temp-dir /var/tmp *.fits
    """
    process_and_view(list(patterns), keep_temp=keep_temp, temp_dir=temp_dir)


@main.command()
@click.argument("patterns", nargs=-1, required=True)
@click.option(
    "--quality",
    "-q",
    default=95,
    type=int,
    help="JPEG quality (1-100, default: 95)",
)
@click.option(
    "--output-dir",
    "-o",
    default=".",
    help="Output directory for converted files (default: current directory)",
)
@click.option(
    "--frame",
    "-f",
    default=None,
    type=int,
    help="Frame index to extract from sequence files (0-based, default: 0)",
)
def tojpg(patterns, quality, output_dir, frame):
    """Convert FITS files to JPG format.

    Converts one or more FITS files to JPEG images.
    Supports both individual FITS files and sequence FITS files.
    For sequence files, use --frame to select which frame to extract.

    Examples:
        zmqcam tojpg one.fits
        zmqcam tojpg *.fits
        zmqcam tojpg --quality 90 *.fits
        zmqcam tojpg -o /tmp/output *.fits
        zmqcam tojpg -f 5 sequence_seq81.fits  # Extract frame 5
    """
    import glob as glob_module
    import os

    # Expand glob patterns
    fits_files = []
    for pattern in patterns:
        matches = glob_module.glob(pattern)
        if matches:
            fits_files.extend(matches)
        elif os.path.isfile(pattern):
            fits_files.append(pattern)

    if not fits_files:
        print("No FITS files found matching the provided patterns")
        return

    # Ensure output directory exists
    os.makedirs(output_dir, exist_ok=True)

    success_count = 0
    for fits_path in fits_files:
        if not fits_path.endswith('.fits'):
            print(f"Skipping non-FITS file: {fits_path}")
            continue

        base_name = os.path.splitext(os.path.basename(fits_path))[0]
        # Add frame number to filename if extracting specific frame
        if frame is not None:
            base_name = f"{base_name}_frame{frame}"
        output_path = os.path.join(output_dir, f"{base_name}.jpg")

        print(f"Converting {fits_path} -> {output_path}...")
        if convert_fits_to_image(fits_path, output_path, format="jpg", quality=quality, frame=frame):
            print(f"  Saved: {output_path}")
            success_count += 1
        else:
            print(f"  Failed to convert {fits_path}")

    print(f"\nConverted {success_count}/{len(fits_files)} files successfully")


@main.command()
@click.argument("patterns", nargs=-1, required=True)
@click.option(
    "--output-dir",
    "-o",
    default=".",
    help="Output directory for converted files (default: current directory)",
)
@click.option(
    "--frame",
    "-f",
    default=None,
    type=int,
    help="Frame index to extract from sequence files (0-based, default: 0)",
)
def topng(patterns, output_dir, frame):
    """Convert FITS files to PNG format.

    Converts one or more FITS files to PNG images.
    Supports both individual FITS files and sequence FITS files.
    For sequence files, use --frame to select which frame to extract.

    Examples:
        zmqcam topng one.fits
        zmqcam topng *.fits
        zmqcam topng -o /tmp/output *.fits
        zmqcam topng -f 5 sequence_seq81.fits  # Extract frame 5
    """
    import glob as glob_module
    import os

    # Expand glob patterns
    fits_files = []
    for pattern in patterns:
        matches = glob_module.glob(pattern)
        if matches:
            fits_files.extend(matches)
        elif os.path.isfile(pattern):
            fits_files.append(pattern)

    if not fits_files:
        print("No FITS files found matching the provided patterns")
        return

    # Ensure output directory exists
    os.makedirs(output_dir, exist_ok=True)

    success_count = 0
    for fits_path in fits_files:
        if not fits_path.endswith('.fits'):
            print(f"Skipping non-FITS file: {fits_path}")
            continue

        base_name = os.path.splitext(os.path.basename(fits_path))[0]
        # Add frame number to filename if extracting specific frame
        if frame is not None:
            base_name = f"{base_name}_frame{frame}"
        output_path = os.path.join(output_dir, f"{base_name}.png")

        print(f"Converting {fits_path} -> {output_path}...")
        if convert_fits_to_image(fits_path, output_path, format="png", frame=frame):
            print(f"  Saved: {output_path}")
            success_count += 1
        else:
            print(f"  Failed to convert {fits_path}")

    print(f"\nConverted {success_count}/{len(fits_files)} files successfully")


@main.command()
@click.argument("patterns", nargs=-1, required=True)
@click.option(
    "--output-dir",
    "-o",
    default=".",
    help="Output directory for converted files (default: current directory)",
)
@click.option(
    "--fps",
    "-f",
    default=30,
    type=int,
    help="Frames per second for the video (default: 30)",
)
@click.option(
    "--quality",
    "-q",
    default=95,
    type=int,
    help="Video quality (1-100, default: 95)",
)
def tomp4(patterns, output_dir, fps, quality):
    """Convert FITS sequence files to MP4 video.

    Converts FITS sequence files (4D RGB cubes) to MP4 video format.
    Only works with sequence files created using burst recording.

    Examples:
        zmqcam tomp4 sequence_seq81.fits
        zmqcam tomp4 -f 15 *.fits
        zmqcam tomp4 -o /tmp/output --fps 24 core6a_*.fits
    """
    import glob as glob_module
    import os

    # Expand glob patterns
    fits_files = []
    for pattern in patterns:
        matches = glob_module.glob(pattern)
        if matches:
            fits_files.extend(matches)
        elif os.path.isfile(pattern):
            fits_files.append(pattern)

    if not fits_files:
        print("No FITS files found matching the provided patterns")
        return

    # Ensure output directory exists
    os.makedirs(output_dir, exist_ok=True)

    success_count = 0
    for fits_path in fits_files:
        if not fits_path.endswith('.fits'):
            print(f"Skipping non-FITS file: {fits_path}")
            continue

        base_name = os.path.splitext(os.path.basename(fits_path))[0]
        output_path = os.path.join(output_dir, f"{base_name}.mp4")

        print(f"Converting {fits_path} -> {output_path}...")
        if convert_fits_to_mp4(fits_path, output_path, fps=fps, quality=quality):
            print(f"  Saved: {output_path}")
            success_count += 1
        else:
            print(f"  Failed to convert {fits_path}")

    print(f"\nConverted {success_count}/{len(fits_files)} files successfully")


if __name__ == "__main__":
    main()
