import os
import logging
import time
import pygame
from pathlib import Path
from PyQt6.QtWidgets import QApplication


def get_settings():
    from PyQt6.QtCore import QSettings

    return QSettings("gasket", "gasket")


class Paths:
    @classmethod
    def resource(cls, *parts):
        base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
        # Check if running from AppImage (bin/src structure)
        if os.path.exists(os.path.join(base, "..", "res")):
            return Path(base, "..", "res", *parts)
        return Path(base, "res", *parts)

    @classmethod
    def sound_path(cls, filename, ui_mode):
        if ui_mode == "sonic":
            return cls.resource("sonic", "sounds", filename)
        return cls.resource(filename)


logger = logging.getLogger(__name__)


class AudioManager:
    def __init__(self, main_window):
        self.main_window = main_window
        self.settings = get_settings()
        self.exit_sound_played = False
        self.mixer_initialized = False
        self.audio_available = True

        self.preview_master_volume = self.settings.value("master_volume", 80, type=int)
        self.preview_effects_volume = self.settings.value(
            "effects_volume", 50, type=int
        )
        self.preview_hum_volume = self.settings.value("hum_volume", 20, type=int)

        self.effects_channel = None
        self.hum_channel = None

        self.setup_sounds()

    def _ensure_mixer_initialized(self):
        if self.mixer_initialized:
            return True

        sdl_driver = os.environ.get("SDL_AUDIODRIVER")
        if sdl_driver:
            logger.info(f"Using SDL_AUDIODRIVER={sdl_driver}")

        try:
            import importlib
            if importlib.util.find_spec("pygame.mixer") is None:
                logger.warning("pygame.mixer module not available")
                self.mixer_initialized = False
                self.audio_available = False
                return False
        except Exception:
            pass

        try:
            logger.info("Initializing pygame mixer...")
            pygame.mixer.init(frequency=44100, size=-16, channels=2, buffer=512)
            self.mixer_initialized = True
            logger.info("Pygame mixer initialized")
            return True
        except Exception as e:
            logger.error(f"Pygame mixer init failed: {e}")
            self.mixer_initialized = False
            self.audio_available = False
            return False

    def applyVolume(self, volumeSliderValue):
        linear_volume = volumeSliderValue / 100.0
        return linear_volume

    def setup_sounds(self, play_open_sound: bool = True):
        logger.debug("Setting up audio sounds...")

        if not self._ensure_mixer_initialized():
            logger.warning("Audio setup aborted - mixer initialization failed")
            self.open_sound = self.close_sound = self.loop_sound = None
            return

        if self.effects_channel is None:
            self.effects_channel = pygame.mixer.Channel(0)
        if self.hum_channel is None:
            self.hum_channel = pygame.mixer.Channel(1)

        try:
            logger.debug("Setting up open sound...")
            open_sound_path = self._resolve_sound_path("etw.wav")
            if open_sound_path.exists():
                logger.debug(f"Loading open sound: {str(open_sound_path)}")
                self.open_sound = pygame.mixer.Sound(str(open_sound_path))
                if (
                    play_open_sound
                    and self.settings.value("play_etw", True, type=bool)
                    and not self.effects_channel.get_busy()
                ):
                    logger.debug("Playing open sound (ETW)")
                    self.effects_channel.play(self.open_sound)
            else:
                logger.warning(f"Could not find open sound: {str(open_sound_path)}")
                self.open_sound = None

            logger.debug("Setting up close sound...")
            close_sound_path = self._resolve_sound_path("lall.wav")
            if close_sound_path.exists():
                logger.debug(f"Loading close sound: {str(close_sound_path)}")
                self.close_sound = pygame.mixer.Sound(str(close_sound_path))
                app = QApplication.instance()
                if app is not None:
                    app.aboutToQuit.connect(self.on_app_about_to_quit)
            else:
                logger.warning(f"Could not find close sound: {str(close_sound_path)}")
                self.close_sound = None

            logger.debug("Setting up loop sound...")
            loop_sound_path = None
            for ext in ("wav", "mp3"):
                candidate = self._resolve_sound_path(f"50hz.{ext}")
                if candidate.exists():
                    loop_sound_path = candidate
                    break

            if loop_sound_path and loop_sound_path.exists():
                logger.debug(f"Loading loop sound: {str(loop_sound_path)}")
                self.loop_sound = pygame.mixer.Sound(str(loop_sound_path))

                if (
                    self.settings.value("play_50hz_hum", True, type=bool)
                    and not self.hum_channel.get_busy()
                ):
                    logger.debug("Starting loop sound playback")
                    self.hum_channel.set_volume(0.0)
                    self.hum_channel.play(self.loop_sound, loops=-1)
            else:
                logger.warning(f"Could not find loop sound: {str(loop_sound_path)}")
                self.loop_sound = None

            logger.debug("Applying initial audio settings...")
            self.apply_audio_settings()
            logger.debug("Audio setup completed successfully")

        except Exception as e:
            logger.error(f"Error during audio setup: {e}")
            self.open_sound = self.close_sound = self.loop_sound = None

    def _resolve_sound_path(self, filename: str):
        ui_mode = self.settings.value("ui_mode", "default")
        return Paths.sound_path(filename, ui_mode)

    def reload_sounds_for_ui_mode(self):
        logger.debug("Reloading sounds for UI mode change")

        if self.effects_channel:
            self.effects_channel.stop()
        if self.hum_channel:
            self.hum_channel.stop()

        self.open_sound = None
        self.close_sound = None
        self.loop_sound = None

        self.setup_sounds(play_open_sound=False)

    def apply_audio_settings(self):
        if (
            not self.mixer_initialized
            or not self.effects_channel
            or not self.hum_channel
        ):
            logger.debug("Audio mixer not initialized, skipping settings application")
            return

        if not any([self.open_sound, self.close_sound, self.loop_sound]):
            logger.debug("Audio not available, skipping settings application")
            return

        logger.debug("Applying audio settings...")

        master_volume = self.applyVolume(
            self.settings.value("master_volume", 80, type=int)
        )
        effects_volume = self.applyVolume(
            self.settings.value("effects_volume", 50, type=int)
        )
        hum_volume = self.applyVolume(self.settings.value("hum_volume", 20, type=int))

        logger.debug(
            f"Volume levels - Master: {master_volume:.3f}, Effects: {effects_volume:.3f}, Hum: {hum_volume:.3f}"
        )

        self.effects_channel.set_volume(master_volume * effects_volume)
        self.hum_channel.set_volume(master_volume * hum_volume)

        play_loop = self.settings.value("play_50hz_hum", True, type=bool)
        if play_loop and not self.hum_channel.get_busy() and self.loop_sound:
            logger.debug("Starting loop sound playback")
            self.hum_channel.play(self.loop_sound, loops=-1)
        elif not play_loop and self.hum_channel.get_busy():
            logger.debug("Stopping loop sound playback")
            self.hum_channel.stop()

    def sync_preview_values_from_settings(self):
        self.preview_master_volume = self.settings.value("master_volume", 80, type=int)
        self.preview_effects_volume = self.settings.value(
            "effects_volume", 50, type=int
        )
        self.preview_hum_volume = self.settings.value("hum_volume", 20, type=int)

    def apply_preview_volumes(self, master=None, effects=None, hum=None):
        if (
            not self.mixer_initialized
            or not self.effects_channel
            or not self.hum_channel
        ):
            return

        if master is not None:
            self.preview_master_volume = master
        if effects is not None:
            self.preview_effects_volume = effects
        if hum is not None:
            self.preview_hum_volume = hum

        master_volume = self.applyVolume(self.preview_master_volume)
        effects_volume = self.applyVolume(self.preview_effects_volume)
        hum_volume = self.applyVolume(self.preview_hum_volume)

        self.effects_channel.set_volume(master_volume * effects_volume)
        self.hum_channel.set_volume(master_volume * hum_volume)

    def apply_master_volume_preview(self, value):
        self.apply_preview_volumes(master=value)

    def apply_effects_volume_preview(self, value):
        self.apply_preview_volumes(effects=value)

    def apply_hum_volume_preview(self, value):
        self.apply_preview_volumes(hum=value)

    def on_app_about_to_quit(self):
        if (
            not self.mixer_initialized
            or not self.effects_channel
            or not self.hum_channel
        ):
            return

        if self.exit_sound_played:
            return

        self.exit_sound_played = True
        logger.debug("Playing exit sound")

        if self.hum_channel.get_busy():
            logger.debug("Stopping loop sound")
            self.hum_channel.stop()

        self.play_close_sound_and_wait()

    def play_close_sound_and_wait(self):
        if not self.mixer_initialized or not self.effects_channel:
            return

        if self.close_sound and self.settings.value("play_lall", True, type=bool):
            logger.debug("Starting close sound playback with blocking wait")

            self.effects_channel.play(self.close_sound)

            sound_length = self.close_sound.get_length()
            max_wait_time = sound_length + 2.0
            start_time = time.time()

            while self.effects_channel.get_busy():
                if time.time() - start_time > max_wait_time:
                    logger.warning("Sound playback timeout, continuing anyway")
                    break

                time.sleep(0.05)

            logger.debug("Close sound finished, continuing shutdown")

    def test_etw_sound(self):
        logger.debug("Testing ETW sound")
        if not self.mixer_initialized or not self.effects_channel:
            return
        if self.open_sound:
            if self.effects_channel.get_busy():
                logger.debug("ETW sound already playing, restarting")
                self.effects_channel.stop()
            self.effects_channel.play(self.open_sound)

    def test_lall_sound(self):
        logger.debug("Testing LALL sound")
        if not self.mixer_initialized or not self.effects_channel:
            return
        if self.close_sound:
            if self.effects_channel.get_busy():
                logger.debug("LALL sound already playing, restarting")
                self.effects_channel.stop()
            self.effects_channel.play(self.close_sound)
