"""Application lifecycle – wires together UI, DLNA networking and playback."""

from __future__ import annotations

import asyncio
import logging
import queue
import threading
from typing import Any

from r36s_dlna_browser.dlna.browser_state import BrowserState
from r36s_dlna_browser.dlna.discovery import DLNADiscovery
from r36s_dlna_browser.dlna.client import DLNAClient
from r36s_dlna_browser.player.gstreamer_backend import GStreamerBackend
from r36s_dlna_browser.platform.runtime import configure_logging, sdl_env_setup
from r36s_dlna_browser.ui.sdl_app import SDLApp

log = logging.getLogger(__name__)


class Application:
    """Top-level application that owns the event loop, UI and networking."""

    def __init__(self) -> None:
        configure_logging()
        sdl_env_setup()
        self.ui_queue: queue.Queue[Any] = queue.Queue()
        self.cmd_queue: queue.Queue[Any] = queue.Queue()

        self.browser_state = BrowserState()
        self.discovery = DLNADiscovery()
        self.client = DLNAClient()
        self.player = GStreamerBackend()
        self.player.set_event_callback(self._handle_player_event)

        self._async_loop: asyncio.AbstractEventLoop | None = None
        self._net_thread: threading.Thread | None = None
        self._shutting_down = False

    # ── public API ───────────────────────────────────────────────

    def run(self) -> None:
        """Start the networking thread and enter the SDL2 main loop."""
        self._start_network_thread()
        self.browser_state.loading = True
        self._schedule(("discover",))

        sdl = SDLApp(
            ui_queue=self.ui_queue,
            cmd_queue=self.cmd_queue,
            browser_state=self.browser_state,
            player=self.player,
        )
        sdl.run()  # blocks until quit

    def shutdown(self) -> None:
        self._shutting_down = True
        self._schedule(("shutdown",))
        if self._net_thread and self._net_thread.is_alive():
            self._net_thread.join(timeout=2)
        self.player.shutdown()
        log.info("Application shut down.")

    # ── networking thread ────────────────────────────────────────

    def _start_network_thread(self) -> None:
        self._net_thread = threading.Thread(
            target=self._run_async_loop, daemon=True, name="dlna-net"
        )
        self._net_thread.start()

    def _run_async_loop(self) -> None:
        self._async_loop = asyncio.new_event_loop()
        asyncio.set_event_loop(self._async_loop)
        try:
            self._async_loop.run_until_complete(self._command_dispatcher())
        finally:
            pending = [
                task
                for task in asyncio.all_tasks(self._async_loop)
                if not task.done()
            ]
            for task in pending:
                task.cancel()
            if pending:
                self._async_loop.run_until_complete(
                    asyncio.gather(*pending, return_exceptions=True)
                )
            self._async_loop.close()

    async def _command_dispatcher(self) -> None:
        """Poll cmd_queue for commands from the UI thread and dispatch them."""
        loop = asyncio.get_running_loop()
        while not self._shutting_down:
            try:
                cmd = await loop.run_in_executor(None, self.cmd_queue.get, True, 0.1)
            except queue.Empty:
                continue
            try:
                await self._handle_command(cmd)
            except Exception as exc:
                log.exception("Error handling command %s", cmd)
                self.browser_state.loading = False
                self.ui_queue.put(("error", str(exc)))

    async def _handle_command(self, cmd: tuple) -> None:
        action = cmd[0]
        if action == "discover":
            servers = await self.discovery.find_servers()
            self.browser_state.set_servers(servers)
            self.browser_state.loading = False
            self.ui_queue.put(("servers_updated",))

        elif action == "browse":
            server_location = cmd[1]
            object_id = cmd[2]
            items = await self.client.browse(server_location, object_id)
            self.browser_state.set_items(items, object_id)
            self.browser_state.loading = False
            self.ui_queue.put(("items_updated",))

        elif action == "play":
            url = cmd[1]
            title = cmd[2] if len(cmd) > 2 else ""
            self.player.play(url)
            self.browser_state.playback_paused = False
            self.ui_queue.put(("playback_started", title))

        elif action == "stop":
            self.player.stop()

        elif action == "toggle_pause":
            paused = self.player.toggle_pause()
            self.browser_state.playback_paused = paused
            self.ui_queue.put(("playback_paused" if paused else "playback_resumed",))

        elif action == "seek":
            self.player.seek(cmd[1])

        elif action == "volume":
            volume = self.player.change_volume(cmd[1])
            self.browser_state.playback_volume = volume

        elif action == "shutdown":
            self._shutting_down = True
            self.player.shutdown()

    # ── helpers ──────────────────────────────────────────────────

    def _schedule(self, cmd: tuple) -> None:
        self.cmd_queue.put(cmd)

    def _handle_player_event(self, event: str, *args: Any) -> None:
        if event == "stopped":
            self.browser_state.playback_paused = False
            self.ui_queue.put(("playback_stopped",))
        elif event == "position" and args:
            self.browser_state.playback_position = float(args[0])
        elif event == "duration" and args:
            self.browser_state.playback_duration = float(args[0])
        elif event == "paused" and args:
            self.browser_state.playback_paused = bool(args[0])
        elif event == "volume" and args:
            self.browser_state.playback_volume = int(args[0])
        elif event == "buffering" and args:
            self.browser_state.playback_buffer_percent = int(args[0])
        elif event == "cache" and args:
            self.browser_state.playback_cache_seconds = float(args[0])
        elif event == "resolution" and args:
            self.browser_state.playback_resolution = str(args[0])
        elif event == "error" and args:
            self.browser_state.playback_paused = False
            self.ui_queue.put(("error", str(args[0])))
