"""Audio player component using afplay/ffplay (macOS) with position tracking."""

from __future__ import annotations

import asyncio
import atexit
import subprocess
import time
from pathlib import Path

import flet as ft

TEAL = "#4ec9b0"


def _format_time(secs: float) -> str:
    total = int(secs)
    hours = total // 3600
    minutes = (total % 3600) // 60
    seconds = total % 60
    if hours > 0:
        return f"{hours}:{minutes:02d}:{seconds:02d}"
    return f"{minutes}:{seconds:02d}"


def _get_duration(mp3: Path) -> float:
    try:
        result = subprocess.run(
            ["ffprobe", "-v", "quiet", "-show_entries", "format=duration",
             "-of", "default=noprint_wrappers=1:nokey=1", str(mp3)],
            capture_output=True, text=True, timeout=3,
        )
        if result.returncode == 0:
            return float(result.stdout.strip())
    except Exception:
        pass
    return 0.0


class AudioPlayer(ft.Container):
    """Audio player using afplay/ffplay with async position tracking."""

    def __init__(self, page: ft.Page, on_position_change=None):
        self._page = page
        self._duration_secs = 0.0
        self._position_secs = 0.0
        self._is_playing = False
        self._process: subprocess.Popen | None = None
        self._current_path: Path | None = None
        self._playback_rate = 1.0
        self._play_start_time = 0.0
        self._play_start_pos = 0.0
        self._on_position_change = on_position_change
        self._update_task = None

        self._play_btn = ft.IconButton(
            icon=ft.Icons.PLAY_ARROW_ROUNDED,
            icon_size=28,
            style=ft.ButtonStyle(bgcolor=ft.Colors.PRIMARY, shape=ft.CircleBorder()),
            tooltip="Play/Pause",
            on_click=self._toggle,
        )
        self._skip_back_btn = ft.IconButton(
            icon=ft.Icons.REPLAY_10,
            icon_size=20,
            tooltip="Skip back 15s",
            on_click=self._skip_back,
        )
        self._skip_fwd_btn = ft.IconButton(
            icon=ft.Icons.FORWARD_10,
            icon_size=20,
            tooltip="Skip forward 15s",
            on_click=self._skip_forward,
        )
        self._speed = ft.Dropdown(
            value="1x",
            width=75,
            dense=True,
            text_size=12,
            options=[ft.dropdown.Option(s) for s in ["0.5x", "0.75x", "1x", "1.25x", "1.5x", "2x"]],
            on_select=self._on_speed,
            border_radius=6,
        )
        self._current_label = ft.Text("0:00", size=12, width=50, text_align=ft.TextAlign.RIGHT)
        self._slider = ft.Slider(
            min=0,
            max=1000,
            value=0,
            expand=True,
            active_color=TEAL,
            on_change_end=self._on_seek,
        )
        self._total_label = ft.Text("0:00", size=12, color=ft.Colors.ON_SURFACE_VARIANT, width=50)

        super().__init__(
            content=ft.Row(
                [
                    self._skip_back_btn,
                    self._play_btn,
                    self._skip_fwd_btn,
                    self._speed,
                    self._current_label,
                    self._slider,
                    self._total_label,
                ],
                spacing=8,
                vertical_alignment=ft.CrossAxisAlignment.CENTER,
            ),
            padding=ft.padding.symmetric(horizontal=12, vertical=8),
            height=56,
            border=ft.border.only(top=ft.BorderSide(1, ft.Colors.OUTLINE_VARIANT)),
            bgcolor=ft.Colors.SURFACE_CONTAINER,
            border_radius=8,
        )
        atexit.register(self.stop)

    @property
    def position_secs(self) -> float:
        return self._position_secs

    def load(self, path: Path | None):
        self.stop()
        if path is None:
            self._current_path = None
            self._duration_secs = 0
            self._position_secs = 0
            self._current_label.value = "0:00"
            self._total_label.value = "0:00"
            self._slider.value = 0
            return
        self._current_path = path
        self._duration_secs = _get_duration(path)
        self._position_secs = 0
        self._current_label.value = "0:00"
        self._total_label.value = _format_time(self._duration_secs)
        self._slider.value = 0

    def _toggle(self, e):
        if self._is_playing:
            self.stop()
            self._page.update()
        else:
            self._play(start_pos=self._position_secs)

    def _play(self, start_pos: float = 0):
        if not self._current_path:
            return
        self.stop()
        self._position_secs = start_pos
        self._is_playing = True
        self._play_btn.icon = ft.Icons.PAUSE_ROUNDED
        self._play_start_time = time.monotonic()
        self._play_start_pos = start_pos
        self._page.update()

        # Launch audio subprocess
        try:
            if start_pos > 0:
                cmd = ["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet",
                       "-ss", str(start_pos)]
                if self._playback_rate != 1.0:
                    cmd.extend(["-af", f"atempo={self._playback_rate}"])
                cmd.append(str(self._current_path))
            else:
                cmd = ["afplay"]
                if self._playback_rate != 1.0:
                    cmd.extend(["--rate", str(self._playback_rate)])
                cmd.append(str(self._current_path))
            self._process = subprocess.Popen(
                cmd, stdin=subprocess.DEVNULL,
                stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
            )
        except FileNotFoundError:
            try:
                cmd = ["ffplay", "-nodisp", "-autoexit", "-loglevel", "quiet",
                       "-ss", str(start_pos)]
                if self._playback_rate != 1.0:
                    cmd.extend(["-af", f"atempo={self._playback_rate}"])
                cmd.append(str(self._current_path))
                self._process = subprocess.Popen(
                    cmd, stdin=subprocess.DEVNULL,
                    stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
                )
            except FileNotFoundError:
                self._is_playing = False
                self._play_btn.icon = ft.Icons.PLAY_ARROW_ROUNDED
                self._page.update()
                return

        # Start async UI update loop
        async def _update_loop():
            while self._is_playing:
                if self._process and self._process.poll() is not None:
                    # Process ended
                    self._is_playing = False
                    self._position_secs = 0
                    self._slider.value = 0
                    self._current_label.value = "0:00"
                    self._play_btn.icon = ft.Icons.PLAY_ARROW_ROUNDED
                    self._page.update()
                    return

                elapsed = (time.monotonic() - self._play_start_time) * self._playback_rate
                self._position_secs = self._play_start_pos + elapsed

                if self._duration_secs > 0:
                    pct = min(1000, int((self._position_secs / self._duration_secs) * 1000))
                    self._slider.value = pct

                self._current_label.value = _format_time(self._position_secs)

                if self._on_position_change:
                    self._on_position_change(self._position_secs)

                self._page.update()
                await asyncio.sleep(0.3)

        self._update_task = self._page.run_task(_update_loop)

    def stop(self):
        self._is_playing = False
        proc = self._process
        self._process = None
        if proc and proc.poll() is None:
            try:
                proc.terminate()
                proc.wait(timeout=2)
            except Exception:
                try:
                    proc.kill()
                except Exception:
                    pass
        self._play_btn.icon = ft.Icons.PLAY_ARROW_ROUNDED

    def _skip_back(self, e):
        new_pos = max(0, self._position_secs - 15)
        if self._is_playing:
            self._play(start_pos=new_pos)
        else:
            self._position_secs = new_pos
            self._current_label.value = _format_time(new_pos)
            if self._duration_secs > 0:
                self._slider.value = int((new_pos / self._duration_secs) * 1000)
            self._page.update()

    def _skip_forward(self, e):
        new_pos = min(self._duration_secs, self._position_secs + 15)
        if self._is_playing:
            self._play(start_pos=new_pos)
        else:
            self._position_secs = new_pos
            self._current_label.value = _format_time(new_pos)
            if self._duration_secs > 0:
                self._slider.value = int((new_pos / self._duration_secs) * 1000)
            self._page.update()

    def _on_speed(self, e):
        self._playback_rate = float(e.control.value.replace("x", ""))
        if self._is_playing:
            self._play(start_pos=self._position_secs)

    def _on_seek(self, e):
        if self._duration_secs > 0:
            new_pos = (e.control.value / 1000) * self._duration_secs
            self._position_secs = new_pos
            self._current_label.value = _format_time(new_pos)
            if self._is_playing:
                self._play(start_pos=new_pos)
            else:
                self._page.update()
