"""Camera subscriber recording module.

This module provides recording functionality for the camera subscriber.
It is part of the subscriber refactoring - see subscriber.py for the main module.
"""

import hashlib
import time
from datetime import datetime
from typing import List, Optional, Tuple

import cv2
import numpy as np

from .image_saving import save_current_frame, unpack_yuyv, yuyv_to_rgb


def get_duration_step(current: int, decreasing: bool = False) -> int:
    """Get progressive step size for duration adjustment.
    
    Intervals:
    1-10s: 1s
    10-30s: 2s
    30-60s: 5s
    60-300s: 30s
    above 300s: 60s
    """
    if decreasing:
        # When decreasing, use the step size of the interval we would land in
        if current <= 10:
            return 1
        elif current <= 30:
            return 1  # Still in 1-10 range when decreasing from 10-30
        elif current <= 60:
            return 2  # In 10-30 range when decreasing from 30-60
        elif current <= 300:
            return 5  # In 30-60 range when decreasing from 60-300
        else:
            return 30  # In 60-300 range when decreasing from 300+
    else:
        # When increasing, use the step size of current interval
        if current < 10:
            return 1
        elif current < 30:
            return 2
        elif current < 60:
            return 5
        elif current < 300:
            return 30
        else:
            return 60


def get_frame_step(current: int, decreasing: bool = False) -> int:
    """Get progressive step size for frame count adjustment.
    
    Intervals:
    1-10: 1
    10-30: 2
    30-100: 5
    100-500: 10
    500-1000: 50
    above 1000: 100
    """
    if decreasing:
        # When decreasing, use the step size of the interval we would land in
        if current <= 10:
            return 1
        elif current <= 30:
            return 1
        elif current <= 100:
            return 2
        elif current <= 500:
            return 5
        elif current <= 1000:
            return 10
        else:
            return 50
    else:
        # When increasing, use the step size of current interval
        if current < 10:
            return 1
        elif current < 30:
            return 2
        elif current < 100:
            return 5
        elif current < 500:
            return 10
        elif current < 1000:
            return 50
        else:
            return 100


def save_frame_as_format(
    frame_data: bytes,
    header: dict,
    format: str,
    convert_yuyv_to_bgr_func,
) -> None:
    """Save current frame as JPG or PNG.

    Args:
        frame_data: Raw frame data bytes.
        header: Frame header dictionary.
        format: 'jpg' or 'png'.
        convert_yuyv_to_bgr_func: Function to convert YUYV to BGR.
    """
    if frame_data is None or header is None:
        print("\n[Error: No frame available to save]")
        return

    try:
        # Get frame info
        width = header.get("width", 0)
        height = header.get("height", 0)
        fmt = header.get("fmt", "")
        hostname = header.get("hostname", "unknown")

        if fmt != "YUYV":
            print(f"\n[Error: Cannot save {fmt} format as image, only YUYV supported]")
            return

        # Convert YUYV to BGR
        frame_bgr = convert_yuyv_to_bgr_func(frame_data, width, height)

        if frame_bgr is None:
            print("\n[Error: Failed to convert frame]")
            return

        # Generate filename
        now = datetime.now()
        fraction = int((now.microsecond / 1_000_000) * 1000)
        ext = format.lower()
        filename = f"{hostname}_{now.strftime('%y%m%d_%H%M%S')}_{fraction:03d}.{ext}"

        # Save image
        if ext == "jpg":
            cv2.imwrite(filename, frame_bgr, [cv2.IMWRITE_JPEG_QUALITY, 95])
        else:
            cv2.imwrite(filename, frame_bgr)

        print(f"\n[Frame saved as {format.upper()}: {filename}]")

    except Exception as e:
        print(f"\n[Error saving frame: {e}]")


class RecordingManager:
    """Manages burst recording functionality.

    This class handles frame and time-limited recording,
    supporting both individual and sequence burst modes.
    """

    def __init__(
        self,
        max_frames_in_fits: int,
        record_duration: int,
        burst_mode: str,
    ) -> None:
        """Initialize the recording manager.

        Args:
            max_frames_in_fits: Maximum number of frames to save in a FITS sequence.
            record_duration: Duration in seconds for time-based recording.
            burst_mode: Burst mode, 'individual' or 'sequence'.
        """
        self.max_frames_in_fits = max_frames_in_fits
        self.record_duration = record_duration
        self.burst_mode = burst_mode.lower()

        self._recording = False
        self._recording_mode: Optional[str] = None  # 'frame' or 'time'
        self._recorded_frames: List[Tuple[bytes, dict]] = []
        self._record_start_time: Optional[float] = None
        self._burst_count = 0

    @property
    def is_recording(self) -> bool:
        """Check if recording is currently active."""
        return self._recording

    @property
    def recording_mode(self) -> Optional[str]:
        """Get current recording mode."""
        return self._recording_mode

    @property
    def burst_count(self) -> int:
        """Get current burst count."""
        return self._burst_count

    @property
    def recorded_frames(self) -> List[Tuple[bytes, dict]]:
        """Get list of recorded frames."""
        return self._recorded_frames

    def toggle_recording(self, mode: str = "frame") -> None:
        """Toggle burst recording on/off.
        
        Args:
            mode: 'frame' for frame-limited, 'time' for time-limited recording.
        """
        if not self._recording:
            # Start recording
            self._recording = True
            self._recording_mode = mode
            self._recorded_frames = []
            self._burst_count = 0
            self._record_start_time = time.time()
            mode_str = "individual FITS" if self.burst_mode == "individual" else "single FITS sequence"
            
            if mode == "time":
                print(f"\n[Recording STARTED ({mode_str} - TIME LIMITED]")
                print(f"  Duration: {self.record_duration}s")
                print(f"  Press 'a' or 't' to stop early, '['/']' to adjust duration")
            else:
                print(f"\n[Recording STARTED ({mode_str} - FRAME LIMITED]")
                print(f"  Frames: {self.max_frames_in_fits}")
                print(f"  Press 'a' or 't' to stop early, ','/'.' to adjust frame count")
        else:
            # Stop recording (manual stop)
            elapsed = time.time() - self._record_start_time if self._record_start_time else 0
            self._recording = False
            if self.burst_mode == "sequence":
                print(f"\n[Manual stop - saving {len(self._recorded_frames)} frames, {elapsed:.1f}s elapsed]")
                self._stop_and_save_sequence()
            else:
                print(f"\n[Individual burst stopped at {self._burst_count} frames, {elapsed:.1f}s elapsed]")
            self._recording_mode = None

    def stop_recording(self) -> bool:
        """Stop recording and save if in sequence mode.
        
        Returns:
            True if sequence was saved, False otherwise.
        """
        if not self._recording:
            return False
            
        elapsed = time.time() - self._record_start_time if self._record_start_time else 0
        self._recording = False
        
        if self.burst_mode == "sequence":
            print(f"\n[Manual stop - saving {len(self._recorded_frames)} frames, {elapsed:.1f}s elapsed]")
            self._stop_and_save_sequence()
            return True
        else:
            print(f"\n[Individual burst stopped at {self._burst_count} frames, {elapsed:.1f}s elapsed]")
            self._recording_mode = None
            return False

    def _stop_and_save_sequence(self) -> None:
        """Stop recording and save accumulated frames to FITS in a Siril-compatible way."""
        if not self._recorded_frames:
            print("\n[No frames recorded]")
            self._recording = False
            return

        try:
            from astropy.io import fits

            # Get info from first frame
            first_header = self._recorded_frames[0][1]
            hostname = first_header.get("hostname", "unknown")
            width = first_header.get("width", 0)
            height = first_header.get("height", 0)
            num_frames = len(self._recorded_frames)

            # Generate filename
            now = datetime.now()
            fraction = int((now.microsecond / 1_000_000) * 1000)
            filename = f"{hostname}_{now.strftime('%y%m%d_%H%M%S')}_{fraction:03d}_seq{num_frames}.fits"

            # Prepare 4D RGB cube (N, 3, H, W) for Siril compatibility
            # Siril expects NAXIS=4 for color sequences: W x H x 3 x N
            # In numpy, this is (N, 3, H, W)
            rgb_cube = np.zeros((num_frames, 3, height, width), dtype=np.uint8)

            # Metadata for the table
            seq_nos = []
            ts_befores = []
            ts_afters = []
            checksums = []

            for i, (frame_data, header) in enumerate(self._recorded_frames):
                h_w = header.get("width", width)
                h_h = header.get("height", height)

                # Unpack and convert to RGB
                Y, U, V = unpack_yuyv(frame_data, h_w, h_h)
                RGB = yuyv_to_rgb(Y, U, V)

                # Transpose from (H, W, 3) to (3, H, W) and store in cube
                if RGB.shape == (height, width, 3):
                    rgb_cube[i] = RGB.transpose(2, 0, 1)

                # Collect metadata
                seq_nos.append(header.get("seq", 0))
                ts_befores.append(header.get("ts_before", ""))
                ts_afters.append(header.get("ts_after", ""))
                checksums.append(header.get("checksum", ""))

            # Create Primary HDU with the RGB cube
            hdu = fits.PrimaryHDU(rgb_cube)
            hdu.header['HOSTNAME'] = hostname
            hdu.header['NFRAMES'] = num_frames
            hdu.header['PIXFMT'] = 'RGB24'
            hdu.header['WIDTH'] = width
            hdu.header['HEIGHT'] = height
            hdu.header['COMMENT'] = 'Color sequence compatible with Siril (N, 3, H, W)'

            # Write FITS file - ONLY the primary HDU for maximum compatibility
            hdul = fits.HDUList([hdu])
            hdul.writeto(filename, overwrite=True)

            print(f"\n[Sequence saved: {filename} ({num_frames} frames)]")

        except Exception as e:
            print(f"\n[Error saving sequence: {e}]")
        finally:
            self._recording = False
            self._recording_mode = None
            self._recorded_frames = []

    def process_frame(
        self,
        header: dict,
        image_bytes: bytes,
        save_current_frame_func,
    ) -> bool:
        """Process a frame during recording.

        Args:
            header: Frame header dictionary.
            image_bytes: Raw frame data bytes.
            save_current_frame_func: Function to save individual frames.

        Returns:
            True if recording should continue, False if completed.
        """
        if not self._recording:
            return True

        # Check limits based on recording mode
        elapsed = time.time() - self._record_start_time if self._record_start_time else 0
        duration_exceeded = elapsed >= self.record_duration
        frames_exceeded = self._burst_count >= self.max_frames_in_fits if self.burst_mode == "individual" else len(self._recorded_frames) >= self.max_frames_in_fits

        # Determine if we should stop based on recording mode
        if self._recording_mode == "time":
            should_stop = duration_exceeded
        else:  # frame mode
            should_stop = frames_exceeded

        if self.burst_mode == "individual":
            # Save individual FITS file
            filepath = save_current_frame_func(header, image_bytes)
            self._burst_count += 1
            
            if self._recording_mode == "time":
                print(f"\n[{self._burst_count} frames, {elapsed:.1f}/{self.record_duration}s] Saved {filepath}")
            else:
                print(f"\n[{self._burst_count}/{self.max_frames_in_fits} frames] Saved {filepath}")

            # Check if we've reached the limit
            if should_stop:
                self._recording = False
                self._recording_mode = None
                reason = f"{self.record_duration}s duration" if self._recording_mode == "time" else f"{self.max_frames_in_fits} frames"
                print(f"\n[Recording completed ({reason})]")
                return False
        else:
            # Store copies of frame data and header for sequence
            self._recorded_frames.append((bytes(image_bytes), header.copy()))
            current_frames = len(self._recorded_frames)
            
            if self._recording_mode == "time":
                print(f"\n[{current_frames} frames, {elapsed:.1f}/{self.record_duration}s]")
            else:
                print(f"\n[{current_frames}/{self.max_frames_in_fits} frames]")

            # Check if we've reached the limit
            if should_stop:
                reason = f"{self.record_duration}s duration" if self._recording_mode == "time" else f"{self.max_frames_in_fits} frames"
                print(f"\n[{reason} reached - saving sequence...]")
                self._stop_and_save_sequence()
                return False

        return True
