#!/usr/bin/env python3
"""
Main Application Module for neo-pod-desktop
Provides a GUI for converting, managing and transferring music to iPod Nano devices.
"""

import os
import sys
import random
import logging
from typing import Optional

from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QWidget, QVBoxLayout,
    QSplitter, QStackedWidget,
)
from PyQt6.QtCore import Qt, QUrl, QTimer
from PyQt6.QtGui import QPixmap
from PyQt6.QtMultimedia import QMediaPlayer, QAudioOutput

from config_loader import ConfigLoader
from device_monitor import DeviceMonitor
from hotkeys import HotkeyManager
from playlist_manager import PlaylistManager
from session_store import SessionStore
from library_cache import get_library_cache
from ui.library_tab import LibraryTab
from ui.ipod_tab import iPodTab
from ui.settings_tab import SettingsTab
from ui.sidebar_widget import SIDEBAR_DEFAULT_WIDTH
from ui.player_header import PlayerHeader
from ui.splash_overlay import SplashOverlay
from artwork.cache import cover_cache

logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)


class MainWindow(QMainWindow):
    """Main application window"""

    def __init__(self):
        super().__init__()

        self.setWindowTitle("neo-pod-desktop")
        self.setMinimumSize(800, 600)

        self.config_loader = ConfigLoader()
        self.hotkey_manager: Optional[HotkeyManager] = None
        self.device_monitor: Optional[DeviceMonitor] = None

        self.playlist_manager = PlaylistManager()
        self.session_store = SessionStore()
        self._sidebar_visible = True
        self._pending_playlist_id: str = ""
        self._pending_playlist_tracks: list = []

        self._saved_volume: int = 80
        self._saved_track_path: str = ""
        self._saved_position_ms: int = 0
        self._saved_playing: bool = False
        self._pending_seek_ms: int = 0

        self._shuffle_active: bool = False
        self._repeat_mode: str = "no_repeat"

        self._auto_sync_worker = None

        self._setup_player()
        self._setup_ui()
        self.library_tab.set_playlist_manager(self.playlist_manager)
        self._load_playlists_into_sidebar()
        self._load_settings()
        self._setup_hotkeys()
        self._wire_signals()

        cache = get_library_cache()
        splash = SplashOverlay(self)
        if len(cache) == 0:
            splash.show()
            QApplication.processEvents()

        self.library_tab._scan_library(on_progress=splash.set_progress)

        if splash.isVisible():
            splash.close()
        self._resume_playback_if_saved()
        self._restore_library_state()
        self._start_device_monitor()

    def _setup_player(self):
        self.player = QMediaPlayer()
        self.audio_output = QAudioOutput()
        self.audio_output.setVolume(self._saved_volume / 100.0)
        self.player.setAudioOutput(self.audio_output)
        self.player.positionChanged.connect(self._on_player_position)
        self.player.durationChanged.connect(self._on_player_duration)
        self.player.mediaStatusChanged.connect(self._on_media_status)

    def _setup_ui(self):
        from ui.sidebar_widget import SidebarWidget

        main_widget = QWidget()
        main_layout = QVBoxLayout(main_widget)
        main_layout.setContentsMargins(0, 0, 0, 0)
        main_layout.setSpacing(0)

        self.player_header = PlayerHeader()
        self.player_header.prev_requested.connect(self._on_prev)
        self.player_header.play_pause_requested.connect(self._on_play_pause)
        self.player_header.next_requested.connect(self._on_next)
        self.player_header.shuffle_requested.connect(self._on_shuffle_toggle)
        self.player_header.repeat_requested.connect(self._on_repeat_cycle)
        self.player_header.position_changed_by_user.connect(self._on_user_seek_frac)
        self.player_header.volume_changed.connect(self._on_volume_changed)
        main_layout.addWidget(self.player_header)

        self.stack = QStackedWidget()

        self.library_tab = LibraryTab(self.config_loader)
        self.library_tab.play_requested.connect(self._on_play_requested)
        self.stack.addWidget(self.library_tab)

        self.ipod_tab = iPodTab()
        self.stack.addWidget(self.ipod_tab)

        self.settings_tab = SettingsTab()
        self.stack.addWidget(self.settings_tab)

        self.stack.setCurrentIndex(0)

        self.sidebar = SidebarWidget()
        self.sidebar.item_selected.connect(self._on_sidebar_item_selected)
        self.sidebar.playlist_selected.connect(self._on_playlist_selected)

        self.splitter = QSplitter(Qt.Orientation.Horizontal)
        self.splitter.addWidget(self.sidebar)
        self.splitter.addWidget(self.stack)
        self.splitter.setStretchFactor(0, 0)
        self.splitter.setStretchFactor(1, 1)
        self.splitter.setSizes([SIDEBAR_DEFAULT_WIDTH, 600])
        self.splitter.setHandleWidth(1)
        mid = self.palette().color(self.palette().ColorRole.Midlight)
        self.splitter.setStyleSheet(f"QSplitter::handle {{ background: {mid.name()}; }}")

        main_layout.addWidget(self.splitter, stretch=1)

        self.statusBar().showMessage("Ready")
        self.setCentralWidget(main_widget)

    def _wire_signals(self):
        self.ipod_tab.device_mounted.connect(self._on_device_mounted)
        self.ipod_tab.device_unmounted.connect(self._on_device_unmounted)
        self.settings_tab.hide_library_buttons_changed.connect(
            self.library_tab.set_buttons_visible
        )
        self.settings_tab.auto_detect_changed.connect(self._on_auto_detect_toggled)

        self.sidebar.tracks_dropped_on_playlist.connect(self._on_tracks_dropped_on_playlist)
        self.sidebar.tracks_dropped_for_nav_transfer.connect(self._on_tracks_dropped_for_nav_transfer)
        self.sidebar.playlist_dropped_for_nav_transfer.connect(self._on_playlist_dropped_for_nav_transfer)
        self.ipod_tab.tracks_dropped_for_transfer.connect(self._on_tracks_dropped_for_transfer)
        self.library_tab.transfer_finished.connect(self._on_transfer_finished_for_playlist)

    def _on_sidebar_item_selected(self, kind: str, value: str):
        if kind == "tab":
            tab_index = {"library": 0, "ipod": 1, "settings": 2}.get(value, 0)
            self.stack.setCurrentIndex(tab_index)
            if tab_index == 0:
                self.library_tab.set_view_mode("table")
        elif kind == "filter":
            self.stack.setCurrentIndex(0)
            self.library_tab.set_view_mode(value)
        elif kind == "playlist_create":
            self._create_playlist(value)
        elif kind == "playlist_rename":
            playlist_id, new_name = value.split("|", 1)
            self._rename_playlist(playlist_id, new_name)
        elif kind == "playlist_delete":
            self._delete_playlist(playlist_id=value)
        elif kind == "playlist_reorder":
            ids = value.split("|") if value else []
            self.playlist_manager.reorder(ids)

    def _on_playlist_selected(self, playlist_id: str):
        self.stack.setCurrentIndex(0)
        playlist = self.playlist_manager.get_by_id(playlist_id)
        if playlist:
            self.library_tab.set_current_playlist_id(playlist_id)
            self.library_tab.apply_filter("playlist", playlist.track_paths)

    def _create_playlist(self, name: str):
        playlist_id = self.playlist_manager.create(name)
        self.sidebar.add_playlist_item(playlist_id, name)

    def _rename_playlist(self, playlist_id: str, new_name: str):
        self.playlist_manager.rename(playlist_id, new_name)

    def _delete_playlist(self, playlist_id: str):
        self.playlist_manager.delete(playlist_id)
        self.sidebar.remove_playlist_item(playlist_id)
        self.library_tab.clear_filter()
        self.sidebar.select_library_music()

    def _on_tracks_dropped_on_playlist(self, playlist_id: str, paths: list):
        self.playlist_manager.add_tracks(playlist_id, paths)
        playlist = self.playlist_manager.get_by_id(playlist_id)
        if playlist:
            self._on_playlist_selected(playlist_id)
        self.statusBar().showMessage(f"Added {len(paths)} track(s) to playlist", 5000)

    def _on_tracks_dropped_for_transfer(self, paths: list):
        self.library_tab.transfer_tracks_by_paths(paths)

    def _on_tracks_dropped_for_nav_transfer(self, paths: list):
        self.stack.setCurrentIndex(0)
        self.library_tab.transfer_tracks_by_paths(paths)

    def _on_playlist_dropped_for_nav_transfer(self, playlist_id: str):
        playlist = self.playlist_manager.get_by_id(playlist_id)
        if not playlist or not playlist.track_paths:
            return

        self.stack.setCurrentIndex(0)
        self._pending_playlist_id = playlist_id
        self._pending_playlist_tracks = list(playlist.track_paths)
        self.library_tab.transfer_tracks_by_paths(playlist.track_paths)

    def _on_transfer_finished_for_playlist(self):
        playlist_id = self._pending_playlist_id
        track_paths = self._pending_playlist_tracks
        self._pending_playlist_id = ""
        self._pending_playlist_tracks = []

        if not playlist_id or not track_paths:
            return

        mount_point = getattr(self.ipod_tab, "current_mount_point", None)
        if not mount_point:
            self.statusBar().showMessage("Cannot create playlist: iPod not mounted", 5000)
            return

        playlist = self.playlist_manager.get_by_id(playlist_id)
        if not playlist:
            return

        self.statusBar().showMessage(f"Creating playlist '{playlist.name}' on iPod...")

        from worker import WorkerThread
        self._pl_worker = WorkerThread(
            task_type="create_playlist",
            mount_point=mount_point,
            playlist_name=playlist.name,
            track_paths=track_paths,
        )
        self._pl_worker.finished_signal.connect(self._on_playlist_creation_finished)
        self._pl_worker.start()

    def _on_playlist_creation_finished(self, success, message, result):
        self.statusBar().showMessage(message, 8000)
        try:
            self._pl_worker.quit()
            self._pl_worker.wait()
        except Exception:
            pass
        self._pl_worker = None

    def _load_playlists_into_sidebar(self):
        for pl in self.playlist_manager.get_all():
            self.sidebar.add_playlist_item(pl.id, pl.name)

    def _on_device_mounted(self, mount_point: str):
        self.library_tab.on_device_mounted(mount_point)
        self.library_tab.refresh_play_counts_from_cache()
        self.statusBar().showMessage(f"iPod mounted at {mount_point}")
        QTimer.singleShot(2000, lambda: self._start_auto_sync(mount_point))

    def _on_device_unmounted(self):
        self.library_tab.on_device_unmounted()
        self.statusBar().showMessage("iPod disconnected")

    def _start_auto_sync(self, mount_point: str):
        if self._auto_sync_worker is not None:
            return
        from worker import WorkerThread
        self._auto_sync_worker = WorkerThread(
            "auto_sync_playcounts", parent=self, mount_point=mount_point,
        )
        self._auto_sync_worker.progress_signal.connect(self._on_auto_sync_progress)
        self._auto_sync_worker.finished_signal.connect(self._on_auto_sync_finished)
        self._auto_sync_worker.start()

    def _on_auto_sync_progress(self, pct: int, msg: str):
        pass

    def _on_auto_sync_finished(self, success: bool, msg: str, _result):
        if success and msg.startswith("Auto-sync complete"):
            self.library_tab.refresh_play_counts_from_cache()
        self._auto_sync_worker = None

    def _toggle_sidebar(self):
        visible = not self.sidebar.isVisible()
        self.sidebar.setVisible(visible)
        if visible:
            self.splitter.setSizes([SIDEBAR_DEFAULT_WIDTH, self.splitter.width() - SIDEBAR_DEFAULT_WIDTH])
        else:
            self.splitter.setSizes([0, self.splitter.width()])
        self._sidebar_visible = visible

    def _start_device_monitor(self):
        auto_detect = self.config_loader.get_boolean("Device", "auto_detect", fallback=True)
        self.device_monitor = DeviceMonitor()
        if auto_detect:
            self.ipod_tab.start_monitoring(self.device_monitor)
            self.device_monitor.start()

    def _on_auto_detect_toggled(self, enabled: bool):
        if not self.device_monitor:
            return
        if enabled:
            self.ipod_tab.start_monitoring(self.device_monitor)
            self.device_monitor.start()
        else:
            self.device_monitor.stop()
            self.ipod_tab.stop_monitoring(self.device_monitor)

    def _load_settings(self):
        config = self.config_loader
        self.settings_tab.load_settings(config)
        hide_buttons = config.get_boolean("Advanced", "hide_library_buttons", fallback=False)
        self.library_tab.set_buttons_visible(not hide_buttons)

        # Migrate Playback section → SessionStore if needed
        self.session_store.migrate_from_config(config)

        self._saved_volume = self.session_store.get_int("last_volume", 80)
        self._saved_track_path = self.session_store.get_str("last_track_path", "")
        self._saved_position_ms = self.session_store.get_int("last_position_ms", 0)
        self._saved_playing = self.session_store.get_bool("last_playing", False)
        self._shuffle_active = self.session_store.get_bool("shuffle_active", False)
        self._repeat_mode = self.session_store.get_str("repeat_mode", "no_repeat")

        self.player_header.set_shuffle_active(self._shuffle_active)
        self.player_header.set_repeat_mode(self._repeat_mode)

        self.audio_output.setVolume(self._saved_volume / 100.0)
        self.player_header.set_volume(self._saved_volume)

    def _save_settings(self):
        config = self.config_loader
        session = self.session_store
        self.settings_tab.save_settings(config)

        session.set("last_volume", self.player_header.volume_slider.value())
        session.set("shuffle_active", self._shuffle_active)
        session.set("repeat_mode", self._repeat_mode)
        if self.current_track_path and os.path.exists(self.current_track_path):
            session.set("last_track_path", self.current_track_path)
            position = self.player.position()
            duration = self.player.duration()
            if duration > 0 and position > duration - 2000:
                position = 0
            session.set("last_position_ms", position)
            session.set("last_playing", self._is_playing_now)
        else:
            session.set("last_track_path", "")
            session.set("last_position_ms", 0)
        session.save()

        state = self.library_tab.get_state()
        config.set("Library", "view_mode", state.get("view_mode", "table"))
        config.set("Library", "filtered_album", state.get("filtered_album", ""))
        config.set("Library", "filtered_playlist_id", state.get("filtered_playlist_id", ""))
        config.save()

    @property
    def current_track_path(self) -> str:
        if self.player.source().isValid():
            return self.player.source().toLocalFile()
        return ""

    @property
    def _is_playing_now(self) -> bool:
        return self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState

    def _setup_hotkeys(self):
        self.hotkey_manager = HotkeyManager(self, self.config_loader)
        self.hotkey_manager.register_all({
            "play_pause": self._on_play_pause,
            "prev_track": self._on_prev,
            "next_track": self._on_next,
            "volume_up": lambda: self._adjust_volume(5),
            "volume_down": lambda: self._adjust_volume(-5),
            "seek_forward": self._seek_forward,
            "seek_backward": self._seek_backward,
            "toggle_sidebar": self._toggle_sidebar,
            "tab_library": lambda: self.stack.setCurrentIndex(0),
            "tab_ipod": lambda: self.stack.setCurrentIndex(1),
            "tab_settings": lambda: self.stack.setCurrentIndex(2),
            "search_focus": self.library_tab._focus_search,
            "select_all": self.library_tab._select_all_current,
            "library_refresh": self.library_tab._on_refresh_library_clicked,
            "ipod_refresh": self.ipod_tab._on_refresh_devices_clicked,
            "delete_selected": self.library_tab._delete_selected_current,
            "edit_metadata": self.library_tab._on_edit_metadata,
            "library_add_files": self.library_tab._on_library_add_files,
        })
        self.settings_tab.set_hotkey_manager(self.hotkey_manager)
        self.settings_tab.refresh_shortcuts_table()

    # ------------------------------------------------------------------
    # Playback
    # ------------------------------------------------------------------

    def _on_play_requested(self, track_data: dict, start_position_ms: int, auto_play: bool):
        path = track_data.get("path", "")
        if not path or not os.path.exists(path):
            return
        self._pending_seek_ms = start_position_ms

        self.player.setSource(QUrl.fromLocalFile(path))
        if auto_play:
            self.player.play()
            self.player_header.set_playing(True)
        else:
            self.player_header.set_playing(False)

        self.player_header.set_track_info(
            f"{track_data['artist']}  \u2014  {track_data['title']}"
        )

        cover_path = track_data.get("cover_path")
        if cover_path and os.path.exists(cover_path):
            self.player_header.set_cover(QPixmap(cover_path), track_data.get("album", ""))
        else:
            artist = track_data.get("artist", "")
            album = track_data.get("album", "")
            cached = cover_cache.get(artist, album) if artist else None
            if cached and os.path.exists(cached):
                self.player_header.set_cover(QPixmap(cached), album)
            else:
                self.player_header.set_cover()

    def _on_play_pause(self):
        if self.player.playbackState() == QMediaPlayer.PlaybackState.PlayingState:
            self.player.pause()
            self.player_header.set_playing(False)
            self.library_tab.on_playing_changed(False)
        elif self.player.playbackState() == QMediaPlayer.PlaybackState.StoppedState:
            self.library_tab.play_track_at_index(
                max(0, self.library_tab.current_playback_index)
            )
        else:
            self.player.play()
            self.player_header.set_playing(True)
            self.library_tab.on_playing_changed(True)

    def _on_prev(self):
        if self.player.position() > 3000:
            self.player.setPosition(0)
            return
        idx = max(0, self.library_tab.current_playback_index - 1)
        self.library_tab.play_track_at_index(idx)

    def _get_next_index(self):
        playable = self.library_tab.playable_row_indices()
        if not playable:
            return None

        current = self.library_tab.current_playback_index

        if self._shuffle_active:
            candidates = [r for r in playable if r != current]
            if not candidates:
                candidates = playable
            return random.choice(candidates)

        try:
            pos = playable.index(current)
        except ValueError:
            pos = -1

        if pos >= 0 and pos < len(playable) - 1:
            return playable[pos + 1]

        if self._repeat_mode == "repeat_all" and playable:
            return playable[0]

        return None

    def _stop_playback(self):
        self.player.stop()
        self.library_tab.on_playing_changed(False)
        self.player_header.set_playing(False)

    def _on_next(self):
        idx = self._get_next_index()
        if idx is not None:
            self.library_tab.play_track_at_index(idx)

    def _seek_forward(self):
        if self.player.duration() <= 0:
            return
        new_pos = min(self.player.duration(), self.player.position() + 5000)
        self.player.setPosition(new_pos)

    def _seek_backward(self):
        if self.player.duration() <= 0:
            return
        new_pos = max(0, self.player.position() - 5000)
        self.player.setPosition(new_pos)

    def _adjust_volume(self, delta: int):
        slider = self.player_header.volume_slider
        new_val = max(0, min(100, slider.value() + delta))
        slider.setValue(new_val)

    def _on_volume_changed(self, value: int):
        if self.audio_output:
            self.audio_output.setVolume(value / 100.0)

    def _on_user_seek_frac(self, value: int):
        duration = self.player.duration()
        if duration <= 0:
            return
        new_pos = int(value / 1000.0 * duration)
        new_pos = max(0, min(duration - 1, new_pos))
        self.player.setPosition(new_pos)

    def _on_player_position(self, position: int):
        duration = self.player.duration()
        self.player_header.set_position(position, duration)
        self.library_tab.on_position_changed(position, duration)

    def _on_player_duration(self, duration: int):
        pos = self.player.position()
        self.player_header.set_position(pos, duration)

    def _on_shuffle_toggle(self):
        self._shuffle_active = not self._shuffle_active
        self.player_header.set_shuffle_active(self._shuffle_active)

    def _on_repeat_cycle(self):
        cycle = {"no_repeat": "repeat_all", "repeat_all": "repeat_one", "repeat_one": "no_repeat"}
        self._repeat_mode = cycle[self._repeat_mode]
        self.player_header.set_repeat_mode(self._repeat_mode)

    def _on_media_status(self, status):
        if status == QMediaPlayer.MediaStatus.EndOfMedia:
            self.library_tab.on_track_ended()

            if self._repeat_mode == "repeat_one":
                self.player.setPosition(0)
                self.player.play()
                return

            next_idx = self._get_next_index()
            if next_idx is not None:
                self.library_tab.play_track_at_index(next_idx)
            else:
                self._stop_playback()
        elif status == QMediaPlayer.MediaStatus.NoMedia:
            self.library_tab.on_playing_changed(False)
            self.player_header.reset_info()
        elif status == QMediaPlayer.MediaStatus.LoadedMedia:
            if self._pending_seek_ms > 0:
                self.player.setPosition(self._pending_seek_ms)
                self._pending_seek_ms = 0

    def _resume_playback_if_saved(self):
        if not self._saved_track_path or not os.path.exists(self._saved_track_path):
            return
        for row in range(self.library_tab.library_table.rowCount()):
            item = self.library_tab.library_table.item(row, 0)
            if item is None:
                continue
            _is_ready, data = item.data(Qt.ItemDataRole.UserRole)
            if data and data.get("path", "") == self._saved_track_path:
                self.library_tab.play_track_at_index(
                    row, start_position_ms=self._saved_position_ms,
                    auto_play=self._saved_playing
                )
                return

    def _restore_library_state(self):
        view_mode = self.config_loader.get("Library", "view_mode", fallback="table")
        filtered_album = self.config_loader.get("Library", "filtered_album", fallback="")
        filtered_playlist_id = self.config_loader.get("Library", "filtered_playlist_id", fallback="")

        if filtered_album:
            self.library_tab.filter_by_album(filtered_album)
        elif filtered_playlist_id:
            pl = self.playlist_manager.get_by_id(filtered_playlist_id)
            if pl:
                self.library_tab.set_current_playlist_id(filtered_playlist_id)
                self.library_tab.apply_filter("playlist", pl.track_paths)
        else:
            self.library_tab.set_view_mode(view_mode)

        self.sidebar.blockSignals(True)
        if filtered_playlist_id:
            self.sidebar.select_playlist_silent(filtered_playlist_id)
        else:
            self.sidebar.select_by_view_mode(view_mode)
        self.sidebar.blockSignals(False)

    def closeEvent(self, event):
        if self.device_monitor:
            self.device_monitor.stop()
            self.ipod_tab.stop_monitoring(self.device_monitor)
        for tab in (self.ipod_tab, self.library_tab):
            if hasattr(tab, 'worker_thread') and tab.worker_thread:
                try:
                    if tab.worker_thread.isRunning():
                        tab.worker_thread.stop()
                except RuntimeError:
                    tab.worker_thread = None
        self._save_settings()
        from PyQt6.QtWidgets import QApplication
        QApplication.processEvents()
        event.accept()


# GTK3 platform theme paths for Linux desktop integration
_GTK3_PLUGIN_PATHS = [
    "/usr/lib/qt6/plugins/platformthemes/libqgtk3.so",
    "/usr/lib/x86_64-linux-gnu/qt6/plugins/platformthemes/libqgtk3.so",
    "/usr/lib64/qt6/plugins/platformthemes/libqgtk3.so",
    "/usr/lib/aarch64-linux-gnu/qt6/plugins/platformthemes/libqgtk3.so",
]


def _setup_linux_theming():
    if sys.platform != "linux":
        return

    if "QT_QPA_PLATFORMTHEME" in os.environ:
        return

    for path in _GTK3_PLUGIN_PATHS:
        if os.path.exists(path):
            os.environ["QT_QPA_PLATFORMTHEME"] = "gtk3"
            logger.debug(f"Using GTK3 platform theme: {path}")
            return

    if os.path.exists("/usr/bin/qt6ct"):
        os.environ["QT_QPA_PLATFORMTHEME"] = "qt6ct"
        logger.debug("Using qt6ct platform theme")
        return

    logger.debug("No GTK platform theme plugin found, using Qt default style")


def main():
    _setup_linux_theming()

    app = QApplication(sys.argv)

    if not os.environ.get("QT_QPA_PLATFORMTHEME"):
        app.setStyle("Fusion")

    window = MainWindow()
    window.show()
    sys.exit(app.exec())


if __name__ == "__main__":
    main()
