"""Optimized audio player specifically designed for network file playback."""

from __future__ import annotations

import logging
import os
import threading
import time
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Callable

import numpy as np
import sounddevice as sd
import soundfile as sf

logger = logging.getLogger(__name__)


class PlaybackState(Enum):
    """Enumeration of possible playback states."""

    STOPPED = "stopped"
    PLAYING = "playing"
    PAUSED = "paused"


@dataclass
class AudioTrack:
    """Data class representing an audio track."""

    file_path: Path
    title: str
    artist: str
    album: str
    duration: float
    file_size: int


class NetworkAudioOptimizer:
    """Network audio playback optimizer - Enhanced version with seek functionality."""

    def __init__(self, buffer_size_mb: int = 10, timeout_seconds: int = 30):
        self.buffer_size_bytes = buffer_size_mb * 1024 * 1024
        self.timeout_seconds = timeout_seconds
        self.is_network_file = False
        self.preload_thread = None
        self.preload_complete = threading.Event()
        self.cached_data = None
        self.cached_sample_rate = None
        self.loading_progress = 0.0
        self.error_message = None

        # Network seek enhancement features
        self._lock = threading.RLock()  # Add thread lock
        self.chunk_cache = {}  # Segment cache: {start_frame: audio_data}
        self.chunk_size_frames = 44100 * 5  # 5 seconds per segment
        self.current_chunk = 0  # Current playing segment index
        self.seek_pending = False  # Whether there is a pending seek request
        self.target_seek_position = 0.0  # Target seek position

    def detect_network_path(self, file_path: Path) -> bool:
        """Detect if this is a network path."""
        path_str = str(file_path).lower()
        network_indicators = [
            "\\",  # Windows network path
            "smb://",  # SMB protocol
            "nfs://",  # NFS protocol
            "http://",  # HTTP
            "https://",  # HTTPS
        ]
        return any(indicator in path_str for indicator in network_indicators)

    def seek_network_position(
        self, file_path: Path, target_position: float, sample_rate: int
    ) -> tuple[np.ndarray | None, bool]:
        """Network file position seeking.

        Args:
            file_path: File path
            target_position: Target position (seconds)
            sample_rate: Sample rate

        Returns
        -------
            Tuple[audio data, whether needs to wait for loading]
        """
        if not self.is_network_file:
            return None, False

        target_frame = int(target_position * sample_rate)
        chunk_index = target_frame // self.chunk_size_frames
        chunk_start_frame = chunk_index * self.chunk_size_frames

        logger.info(f"Seeking to position: {target_position:.2f}s (frame {target_frame}, segment {chunk_index})")

        # Check if already in cache
        if chunk_start_frame in self.chunk_cache:
            logger.info(f"Segment {chunk_index} is already cached")
            # Return relative position of cached data
            chunk_data = self.chunk_cache[chunk_start_frame]
            relative_frame = target_frame - chunk_start_frame
            if relative_frame < len(chunk_data):
                # Extract data starting from target position
                remaining_data = chunk_data[relative_frame:]
                return remaining_data, False

        # Need to load new segment
        logger.info(f"Need to load segment {chunk_index}")
        self.seek_pending = True
        self.target_seek_position = target_position

        # Start background loading
        def load_chunk_background():
            try:
                chunk_data = self._load_audio_chunk(file_path, chunk_start_frame, self.chunk_size_frames)
                if chunk_data is not None:
                    with self._lock:
                        self.chunk_cache[chunk_start_frame] = chunk_data
                        logger.info(f"Segment {chunk_index} loaded, length: {len(chunk_data)} frames")
                        self.seek_pending = False
            except Exception as e:
                logger.error(f"Segment loading failed: {e}")
                self.seek_pending = False

        load_thread = threading.Thread(target=load_chunk_background, daemon=True)
        load_thread.start()

        return None, True  # Need to wait for loading

    def _load_audio_chunk(self, file_path: Path, start_frame: int, num_frames: int) -> np.ndarray | None:
        """Load specified segment of audio file."""
        try:
            with sf.SoundFile(str(file_path)) as f:
                # Move to start position
                f.seek(start_frame)
                # Read specified frames
                chunk_data = f.read(frames=num_frames, dtype="float32")

                if chunk_data is not None and len(chunk_data) > 0:
                    # Convert to stereo
                    if chunk_data.ndim == 1:
                        chunk_data = np.column_stack([chunk_data, chunk_data])
                    return chunk_data
        except Exception as e:
            logger.error(f"Loading audio segment failed: {e}")

        return None

    def preload_adjacent_chunks(self, file_path: Path, current_position: float, sample_rate: int) -> None:
        """Preload adjacent segments to improve seek response speed."""
        if not self.is_network_file:
            return

        current_frame = int(current_position * sample_rate)
        current_chunk = current_frame // self.chunk_size_frames

        # Preload one segment before and after
        for offset in [-1, 1]:
            adjacent_chunk = current_chunk + offset
            if adjacent_chunk < 0:
                continue

            start_frame = adjacent_chunk * self.chunk_size_frames
            if start_frame not in self.chunk_cache:
                # Background preload
                def preload_background(chunk_idx, start_frm):
                    try:
                        chunk_data = self._load_audio_chunk(file_path, start_frm, self.chunk_size_frames)
                        if chunk_data is not None:
                            with self._lock:
                                self.chunk_cache[start_frm] = chunk_data
                                logger.debug(f"Preload segment {chunk_idx} completed")
                    except Exception as e:
                        logger.debug(f"Preload segment {chunk_idx} failed: {e}")

                preload_thread = threading.Thread(
                    target=lambda chunk_idx=adjacent_chunk, start_frm=start_frame: preload_background(
                        chunk_idx, start_frm
                    ),
                    daemon=True,
                )
                preload_thread.start()

    def load_audio_with_timeout(self, file_path: Path) -> tuple[np.ndarray | None, int | None]:
        """Audio loading with timeout control."""
        result_data = None
        result_sr = None
        error_occurred = threading.Event()

        def load_target():
            nonlocal result_data, result_sr
            try:
                logger.info(f"Starting to load audio file: {file_path}")

                # Detect if this is a network file
                self.is_network_file = self.detect_network_path(file_path)

                if self.is_network_file:
                    logger.info("Detected network file, enabling optimized loading")
                    result_data, result_sr = self._load_network_audio(file_path)
                else:
                    # Local files use standard loading
                    result_data, result_sr = sf.read(str(file_path), dtype="float32")

                    # Convert to stereo
                    if result_data.ndim == 1:
                        result_data = np.column_stack([result_data, result_data])

                logger.info(f"Audio loading completed: {result_sr}Hz, {len(result_data)} frames")

            except Exception as e:
                logger.error(f"Audio loading failed: {e}")
                self.error_message = str(e)
                error_occurred.set()

        # Execute loading in separate thread
        load_thread = threading.Thread(target=load_target, daemon=True)
        load_thread.start()

        # Wait for loading completion or timeout
        load_thread.join(timeout=self.timeout_seconds)

        if load_thread.is_alive():
            logger.warning(f"Audio loading timeout ({self.timeout_seconds} seconds)")
            self.error_message = "Loading timeout, please check network connection"
            return None, None

        if error_occurred.is_set():
            return None, None

        return result_data, result_sr

    def _load_network_audio(self, file_path: Path) -> tuple[np.ndarray, int]:
        """Optimized network audio loading."""
        try:
            # First get file info without full loading
            with sf.SoundFile(str(file_path)) as f:
                total_frames = f.frames
                sample_rate = f.samplerate
                channels = f.channels

                logger.info(f"File info: {total_frames} frames, {sample_rate}Hz, {channels} channels")

                # Calculate reasonable buffer size
                bytes_per_frame = channels * 4  # float32
                max_frames = min(total_frames, self.buffer_size_bytes // bytes_per_frame)

                logger.info(
                    f"Will load first {max_frames}/{total_frames} frames ({max_frames / total_frames * 100:.1f}%)"
                )

                # Chunk loading to provide progress feedback
                chunk_size = min(44100, max_frames)  # Load up to 1 second of data at a time
                loaded_frames = 0
                audio_chunks = []

                while loaded_frames < max_frames:
                    frames_to_read = min(chunk_size, max_frames - loaded_frames)
                    chunk = f.read(frames=frames_to_read, dtype="float32")

                    if len(chunk) == 0:
                        break

                    audio_chunks.append(chunk)
                    loaded_frames += len(chunk)
                    self.loading_progress = loaded_frames / max_frames

                    logger.debug(f"Loading progress: {self.loading_progress * 100:.1f}%")

                # Merge all chunks
                if audio_chunks:
                    audio_data = np.concatenate(audio_chunks, axis=0)
                else:
                    raise Exception("Failed to read any audio data")

                # Convert to stereo
                if audio_data.ndim == 1:
                    audio_data = np.column_stack([audio_data, audio_data])

                return audio_data, sample_rate

        except Exception as e:
            logger.error(f"Network audio loading failed: {e}")
            raise


class StreamBufferManager:
    """Stream buffer manager, implementing efficient audio data stream processing."""

    def __init__(self, buffer_size: int = 1024 * 1024):  # 1MB buffer
        self.buffer_size = buffer_size
        self.buffers = {}  # File path -> buffer mapping
        self.access_times = {}  # Access time records
        self.max_buffers = 10  # Maximum buffer count

    def get_buffer(self, file_path: Path) -> np.ndarray | None:
        """Get buffer data for the file."""
        key = str(file_path)
        if key in self.buffers:
            self.access_times[key] = time.time()
            return self.buffers[key]
        return None

    def set_buffer(self, file_path: Path, data: np.ndarray) -> None:
        """Set file buffer data."""
        key = str(file_path)
        # If buffer is full, remove least recently used
        if len(self.buffers) >= self.max_buffers:
            self._evict_lru()

        self.buffers[key] = data
        self.access_times[key] = time.time()

    def _evict_lru(self) -> None:
        """Remove least recently used buffer."""
        if not self.access_times:
            return
        lru_key = min(self.access_times.keys(), key=self.access_times.get)
        del self.buffers[lru_key]
        del self.access_times[lru_key]

    def clear_old_buffers(self, max_age: float = 300) -> None:
        """Clear buffers that exceed specified time."""
        current_time = time.time()
        keys_to_remove = [key for key, access_time in self.access_times.items() if current_time - access_time > max_age]
        for key in keys_to_remove:
            del self.buffers[key]
            del self.access_times[key]


class OptimizedAudioPlayer:
    """Optimized audio player."""

    def __init__(self) -> None:
        """Initialize optimized audio player with streaming support."""
        self._state: PlaybackState = PlaybackState.STOPPED
        self._stream_buffer = StreamBufferManager()  # Add streaming buffer management
        self._current_track: AudioTrack | None = None
        self._volume: float = 0.7
        self._position: float = 0.0
        self._lock = threading.RLock()
        self._progress_callback: Callable[[float], None] | None = None
        self._play_thread: threading.Thread | None = None
        self._should_stop = threading.Event()
        self._audio_stream = None
        self._audio_data = None
        self._sample_rate = None
        self._current_frame = 0
        self._optimizer = NetworkAudioOptimizer()

        # Status monitoring
        self._loading_status = "Ready"
        self._last_error = None

    @property
    def state(self) -> PlaybackState:
        """Get current playback state."""
        with self._lock:
            return self._state

    @property
    def current_track(self) -> AudioTrack | None:
        """Get currently loaded track."""
        with self._lock:
            return self._current_track

    @property
    def volume(self) -> float:
        """Get current volume level (0.0 to 1.0)."""
        with self._lock:
            return self._volume

    @volume.setter
    def volume(self, value: float) -> None:
        """Set volume level with validation."""
        with self._lock:
            self._volume = max(0.0, min(1.0, value))

    @property
    def position(self) -> float:
        """Get current playback position in seconds."""
        with self._lock:
            return self._position

    @property
    def loading_status(self) -> str:
        """Get loading status."""
        return self._loading_status

    @property
    def last_error(self) -> str | None:
        """Get last error message."""
        return self._last_error

    def _update_loading_status(self, status: str) -> None:
        """Update loading status."""
        self._loading_status = status
        logger.info(f"Loading status: {status}")

    def _load_audio_file(self, file_path: Path) -> bool:
        """Optimized audio file loading, with enhanced error handling."""
        try:
            self._update_loading_status("Detecting file...")

            # Diagnose file issues
            diagnosis = self.diagnose_file_issues(file_path)
            logger.info(f"File diagnosis result: {diagnosis}")

            if not diagnosis["file_exists"]:
                self._last_error = "File does not exist"
                self._update_loading_status("File does not exist")
                return False

            if not diagnosis["file_readable"]:
                self._last_error = "File is not readable, please check permissions"
                self._update_loading_status("File permission error")
                return False

            # Use enhanced error handling to load audio
            self._update_loading_status("Starting to load audio...")
            audio_data, sample_rate = self._load_with_enhanced_error_handling(file_path)

            if audio_data is None or sample_rate is None:
                self._last_error = self._optimizer.error_message or "Unknown error"
                self._update_loading_status(f"Loading failed: {self._last_error}")
                return False

            self._audio_data = audio_data
            self._sample_rate = sample_rate

            self._update_loading_status("Loading completed")
            logger.info(f"Audio file loaded successfully: {file_path.name} ({sample_rate}Hz)")
            return True

        except Exception as e:
            self._last_error = str(e)
            self._update_loading_status(f"Loading exception: {e}")
            logger.error(f"Audio file loading exception: {e}")
            return False

    def set_progress_callback(self, callback: Callable[[float], None]) -> None:
        """Set callback function for progress updates."""
        with self._lock:
            self._progress_callback = callback

    def load_track(self, track: AudioTrack) -> bool:
        """Load an audio track for playback with optimization."""
        with self._lock:
            try:
                self._update_loading_status("Checking file...")

                if not track.file_path.exists():
                    self._last_error = f"File does not exist: {track.file_path}"
                    logger.error(self._last_error)
                    return False

                # Stop current playback
                self._stop_internal()
                self._update_loading_status("Stopping current playback")

                # Load new track with optimization
                self._update_loading_status("Starting to load audio...")
                if not self._load_audio_file(track.file_path):
                    return False

                self._current_track = track
                self._state = PlaybackState.STOPPED
                self._position = 0.0
                self._current_frame = 0

                self._update_loading_status("Track loading completed")
                logger.info(f"Track loaded successfully: {track.title} by {track.artist}")
                return True

            except Exception as e:
                self._last_error = str(e)
                self._update_loading_status(f"Loading error: {e}")
                logger.error(f"Track loading failed: {e}")
                return False

    def play(self) -> bool:
        """Start playback of currently loaded track."""
        with self._lock:
            if self._current_track is None:
                self._last_error = "No track loaded"
                logger.warning(self._last_error)
                return False

            try:
                self._should_stop.clear()
                self._play_thread = threading.Thread(target=self._play_audio_stream, daemon=True)
                self._play_thread.start()
                self._state = PlaybackState.PLAYING
                self._update_loading_status("Playing")
                logger.info(f"Starting playback: {self._current_track.title}")
                return True

            except Exception as e:
                self._last_error = str(e)
                logger.error(f"Playback start failed: {e}")
                return False

    def pause(self) -> None:
        """Pause current playback."""
        with self._lock:
            if self._state == PlaybackState.PLAYING:
                self._should_stop.set()
                if self._play_thread:
                    self._play_thread.join(timeout=1.0)
                self._state = PlaybackState.PAUSED
                self._update_loading_status("Paused")
                logger.info("Playback paused")

    def resume(self) -> bool:
        """Resume paused playback."""
        with self._lock:
            if self._state == PlaybackState.PAUSED:
                try:
                    return self.play()
                except Exception as e:
                    self._last_error = str(e)
                    logger.error(f"Resume playback failed: {e}")
                    return False
            return False

    def stop(self) -> None:
        """Stop playback and reset position."""
        with self._lock:
            self._stop_internal()
            self._update_loading_status("Stopped")
            logger.info("Playback stopped")

    def seek(self, position: float) -> bool:
        """Seek to specific position in current track.

        Args:
            position: Position in seconds

        Returns
        -------
            True if seek successful, False otherwise
        """
        logger.info(f"seek() called with position={position:.2f}s")

        # Check playback state before acquiring lock to avoid deadlock
        was_playing = self._state == PlaybackState.PLAYING
        logger.info(f"Current state: {self._state.value}, was_playing={was_playing}")

        with self._lock:
            logger.debug("Lock acquired")

            if self._current_track is None or self._sample_rate is None:
                logger.warning("Cannot seek: No track loaded or sample rate unavailable")
                return False

            logger.info(f"Current track: {self._current_track.title}, sample_rate={self._sample_rate}Hz")

            try:
                # For network files, use special seek handling
                if self._optimizer.is_network_file and self._optimizer.seek_network_position:
                    logger.info(f"Handling network file seek: {position:.2f}s")

                    # Call network seek method
                    audio_chunk, needs_wait = self._optimizer.seek_network_position(
                        self._current_track.file_path, position, self._sample_rate
                    )

                    if audio_chunk is not None:
                        # Use newly loaded audio data
                        self._audio_data = audio_chunk
                        self._current_frame = 0
                        self._position = position
                        logger.info(f"Network seek successful: {position:.2f}s")

                        # If was playing, automatically resume playback
                        if was_playing:
                            logger.info("Stopping old playback thread and starting new thread")
                            self._should_stop.set()
                            if self._play_thread:
                                self._play_thread.join(timeout=1.0)
                                logger.info("Old playback thread stopped")

                            # Start new playback thread
                            self._should_stop.clear()
                            self._play_thread = threading.Thread(target=self._play_audio_stream, daemon=True)
                            self._play_thread.start()
                            self._state = PlaybackState.PLAYING
                            logger.info("New playback thread started")
                        return True
                    if needs_wait:
                        # Need to wait for background loading to complete
                        logger.info(f"Network seek needs to wait for loading: {position:.2f}s")
                        # Here can set waiting state or return special flag
                        return False
                    logger.warning(f"Network seek failed: {position:.2f}s")
                    return False

                # Local files use original logic
                if self._audio_data is None:
                    logger.warning("Cannot seek: Audio data unavailable")
                    return False

                logger.info(f"Audio data available: {len(self._audio_data)} frames")

                # Calculate frame position
                target_frame = int(position * self._sample_rate)
                total_frames = len(self._audio_data)

                logger.info(f"Target frame: {target_frame}, Total frames: {total_frames}")

                # Validate bounds
                if 0 <= target_frame < total_frames:
                    # Update internal state
                    self._current_frame = target_frame
                    self._position = position

                    logger.info(f"Seeked to position: {position:.2f}s (frame {target_frame})")

                    # If was playing, automatically resume playback
                    if was_playing:
                        logger.info("Stopping old playback thread and starting new thread")
                        self._should_stop.set()
                        if self._play_thread:
                            self._play_thread.join(timeout=1.0)
                            logger.info("Old playback thread stopped")

                        # Start new playback thread
                        self._should_stop.clear()
                        self._play_thread = threading.Thread(target=self._play_audio_stream, daemon=True)
                        self._play_thread.start()
                        self._state = PlaybackState.PLAYING
                        logger.info("New playback thread started")
                    return True
                logger.warning(f"Seek position out of bounds: {position}s (frame {target_frame}, total {total_frames})")
                return False

            except Exception as e:
                logger.error(f"Failed to seek to position {position}: {e}")
                import traceback

                traceback.print_exc()
                return False

    def _play_audio_stream(self) -> None:
        """Play audio using sounddevice stream."""
        if self._audio_data is None or self._sample_rate is None:
            self._last_error = "No audio data"
            logger.error(self._last_error)
            return

        try:
            # Audio callback function
            def audio_callback(outdata, frames, time, status):
                if status:
                    logger.warning(f"Audio callback status: {status}")

                if self._should_stop.is_set():
                    outdata.fill(0)
                    raise sd.CallbackStop()

                # Calculate end frame
                end_frame = self._current_frame + frames
                if end_frame > len(self._audio_data):
                    # End of audio
                    remaining = len(self._audio_data) - self._current_frame
                    if remaining > 0:
                        outdata[:remaining] = self._audio_data[self._current_frame : end_frame] * self._volume
                        outdata[remaining:].fill(0)
                    else:
                        outdata.fill(0)
                    raise sd.CallbackStop()
                # Normal playback
                outdata[:] = self._audio_data[self._current_frame : end_frame] * self._volume
                self._current_frame = end_frame

                # Update position
                with self._lock:
                    self._position = self._current_frame / self._sample_rate

                    # Intelligent prefetch: periodically preload adjacent segments
                    if (
                        self._current_frame % (self._sample_rate * 10) == 0  # Check every 10 seconds
                        and self._optimizer.is_network_file
                        and hasattr(self._optimizer, "preload_adjacent_chunks")
                    ):
                        try:
                            self._optimizer.preload_adjacent_chunks(
                                self._current_track.file_path,
                                self._position,
                                self._sample_rate,
                            )
                        except Exception as e:
                            logger.debug(f"Prefetch failed: {e}")

                    # Call progress callback if set
                    if self._progress_callback:
                        self._progress_callback(self._position)

            # Create and start audio stream
            with sd.OutputStream(
                samplerate=self._sample_rate,
                channels=self._audio_data.shape[1],
                callback=audio_callback,
                blocksize=1024,
            ) as stream:
                self._audio_stream = stream
                stream.start()

                # Non-blocking wait for playback completion
                while stream.active and not self._should_stop.is_set():
                    time.sleep(0.1)

        except Exception as e:
            self._last_error = str(e)
            logger.error(f"Audio playback error: {e}")
        finally:
            with self._lock:
                if not self._should_stop.is_set():
                    self._state = PlaybackState.STOPPED
                    self._position = 0.0
                    self._current_frame = 0

    def _stop_internal(self) -> None:
        """Stop playback without logging."""
        self._should_stop.set()
        if self._play_thread:
            self._play_thread.join(timeout=1.0)
        self._state = PlaybackState.STOPPED
        self._position = 0.0

    def cleanup(self) -> None:
        """Clean up audio resources."""
        with self._lock:
            self._stop_internal()
            self._audio_data = None
            self._sample_rate = None
            self._current_track = None
            self._update_loading_status("Resources cleaned up")

    def get_supported_formats(self) -> list[str]:
        """Get list of supported audio formats."""
        return ["wav", "mp3", "flac", "ogg", "aiff"]

    def is_supported_format(self, file_path: Path) -> bool:
        """Check if file format is supported."""
        return file_path.suffix.lower()[1:] in self.get_supported_formats()

    def _load_with_enhanced_error_handling(self, file_path: Path) -> tuple[np.ndarray | None, int | None]:
        """Use enhanced error handling to load audio file."""
        errors = []

        # Method 1: Standard soundfile loading
        try:
            logger.info(f"Method 1 - Standard loading: {file_path}")
            audio_data, sample_rate = sf.read(str(file_path), dtype="float32")

            # Convert to stereo
            if audio_data.ndim == 1:
                audio_data = np.column_stack([audio_data, audio_data])

            logger.info(f"Standard loading successful: {len(audio_data)} frames, {sample_rate}Hz")
            return audio_data, sample_rate
        except Exception as e:
            error_msg = f"Standard loading failed: {e!s}"
            logger.warning(error_msg)
            errors.append(error_msg)

        # Method 2: Chunk loading
        try:
            logger.info(f"Method 2 - Chunk loading: {file_path}")
            with sf.SoundFile(str(file_path)) as f:
                total_frames = f.frames
                sample_rate = f.samplerate

                # Small chunk loading test
                chunk_size = min(10000, total_frames)  # Load small chunk for testing first
                audio_data = f.read(frames=chunk_size, dtype="float32")

                if audio_data is not None and len(audio_data) > 0:
                    # Convert to stereo
                    if audio_data.ndim == 1:
                        audio_data = np.column_stack([audio_data, audio_data])
                    logger.info(f"Chunk loading successful: {len(audio_data)} frames, {sample_rate}Hz")
                    return audio_data, sample_rate
        except Exception as e:
            error_msg = f"Chunk loading failed: {e!s}"
            logger.warning(error_msg)
            errors.append(error_msg)

        # Method 3: File info check
        try:
            logger.info(f"Method 3 - File info check: {file_path}")
            info = sf.info(str(file_path))
            logger.info(f"File info - Sample rate:{info.samplerate}, Channels:{info.channels}, Frames:{info.frames}")
            logger.info(f"Format:{info.format}, Subtype:{info.subtype}")
        except Exception as e:
            error_msg = f"File info check failed: {e!s}"
            logger.error(error_msg)
            errors.append(error_msg)

        # All methods failed
        error_summary = "; ".join(errors)
        raise Exception(f"All audio loading methods failed: {error_summary}")

    def diagnose_file_issues(self, file_path: Path) -> dict:
        """Diagnose file issues."""
        diagnosis = {
            "file_exists": False,
            "file_readable": False,
            "file_size": 0,
            "soundfile_info": None,
            "errors": [],
        }

        try:
            # Check file existence
            diagnosis["file_exists"] = file_path.exists()
            if not diagnosis["file_exists"]:
                diagnosis["errors"].append("File does not exist")
                return diagnosis

            # Check file readability
            diagnosis["file_size"] = file_path.stat().st_size
            diagnosis["file_readable"] = os.access(file_path, os.R_OK)
            if not diagnosis["file_readable"]:
                diagnosis["errors"].append("File not readable")
                return diagnosis

            # Get soundfile info
            try:
                info = sf.info(str(file_path))
                diagnosis["soundfile_info"] = {
                    "samplerate": info.samplerate,
                    "channels": info.channels,
                    "frames": info.frames,
                    "duration": info.duration,
                    "format": info.format,
                    "subtype": info.subtype,
                }
            except Exception as e:
                diagnosis["errors"].append(f"soundfile unable to read file info: {e!s}")

        except Exception as e:
            diagnosis["errors"].append(f"Error occurred during diagnosis: {e!s}")

        return diagnosis
