import os
from typing import Any, Optional

try:
    import spotipy
    from spotipy.oauth2 import SpotifyOAuth
except ModuleNotFoundError:
    spotipy = None
    SpotifyOAuth = None

try:
    from dotenv import load_dotenv
except ModuleNotFoundError:
    def load_dotenv() -> bool:  # type: ignore[override]
        return False


class SpotifyClient:
    def __init__(self) -> None:
        if spotipy is None or SpotifyOAuth is None:
            raise RuntimeError(
                "Missing dependency 'spotipy'. Run: pip install -r requirements.txt"
            )

        load_dotenv()
        client_id = os.getenv("SPOTIPY_CLIENT_ID") or os.getenv("SPOTIFY_CLIENT_ID")
        client_secret = os.getenv("SPOTIPY_CLIENT_SECRET") or os.getenv("SPOTIFY_CLIENT_SECRET")
        redirect_uri = os.getenv("SPOTIPY_REDIRECT_URI") or os.getenv("SPOTIFY_REDIRECT_URI")

        missing_envs = []
        if not client_id:
            missing_envs.append("SPOTIPY_CLIENT_ID (or SPOTIFY_CLIENT_ID)")
        if not client_secret:
            missing_envs.append("SPOTIPY_CLIENT_SECRET (or SPOTIFY_CLIENT_SECRET)")
        if not redirect_uri:
            missing_envs.append("SPOTIPY_REDIRECT_URI (or SPOTIFY_REDIRECT_URI)")
        if missing_envs:
            formatted = ", ".join(missing_envs)
            raise RuntimeError(f"Missing environment variables: {formatted}")

        scope = (
            "user-modify-playback-state "
            "user-read-playback-state "
            "user-read-currently-playing"
        )
        self.sp = spotipy.Spotify(
            auth_manager=SpotifyOAuth(
                client_id=client_id,
                client_secret=client_secret,
                redirect_uri=redirect_uri,
                scope=scope,
                cache_path=".cache",
            )
        )

    def _active_device_id(self) -> Optional[str]:
        try:
            devices = self.sp.devices().get("devices", [])
        except Exception:
            return None

        if not devices:
            return None
        for device in devices:
            if device.get("is_active"):
                return device.get("id")
        return devices[0].get("id")

    def _current_playback(self) -> Optional[dict[str, Any]]:
        try:
            return self.sp.current_playback()
        except Exception:
            return None

    def current_song(self) -> str:
        playback = self._current_playback()
        if not playback or not playback.get("item"):
            return "Nothing playing"

        item = playback["item"]
        artist_names = ", ".join(artist["name"] for artist in item.get("artists", []))
        return f"{item.get('name', 'Unknown')} - {artist_names or 'Unknown artist'}"

    def play_specific_track(self, track_name: str) -> str:
        if not track_name:
            return "Tell me what to play, for example: play Numb by Linkin Park."

        try:
            results = self.sp.search(q=track_name, limit=1, type="track")
        except Exception as exc:
            return f"Spotify search failed: {exc}"

        items = results.get("tracks", {}).get("items", [])
        if not items:
            return f"Could not find '{track_name}'."

        track = items[0]
        track_uri = track["uri"]
        track_name = track.get("name", "Unknown song")
        artist_name = track.get("artists", [{}])[0].get("name", "Unknown artist")

        device_id = self._active_device_id()
        if not device_id:
            return "No Spotify device found. Open Spotify on phone/desktop and try again."

        try:
            self.sp.start_playback(device_id=device_id, uris=[track_uri])
        except Exception as exc:
            return f"Could not start playback: {exc}"

        return f"Playing {track_name} by {artist_name}."

    def toggle_playback(self, action: str) -> str:
        device_id = self._active_device_id()
        if not device_id:
            return "No Spotify device found. Open Spotify and start a device first."

        if action == "pause":
            try:
                self.sp.pause_playback(device_id=device_id)
            except Exception as exc:
                return f"Could not pause playback: {exc}"
            return "Paused playback."
        if action == "resume":
            try:
                self.sp.start_playback(device_id=device_id)
            except Exception as exc:
                return f"Could not resume playback: {exc}"
            return "Resumed playback."
        return "Unknown playback action."

    def skip_track(self) -> str:
        device_id = self._active_device_id()
        if not device_id:
            return "No active Spotify device."
        try:
            self.sp.next_track(device_id=device_id)
        except Exception as exc:
            return f"Could not skip track: {exc}"
        return "Skipped to next track."

    def previous_track(self) -> str:
        device_id = self._active_device_id()
        if not device_id:
            return "No active Spotify device."
        try:
            self.sp.previous_track(device_id=device_id)
        except Exception as exc:
            return f"Could not go to previous track: {exc}"
        return "Went to previous track."

    def set_volume(self, volume_percent: int) -> str:
        volume_percent = max(0, min(100, int(volume_percent)))
        device_id = self._active_device_id()
        if not device_id:
            return "No active Spotify device."
        try:
            self.sp.volume(volume_percent, device_id=device_id)
        except Exception as exc:
            return f"Could not set volume: {exc}"
        return f"Volume set to {volume_percent}%."

    def adjust_volume(self, delta: int) -> str:
        playback = self._current_playback()
        if not playback:
            return "Nothing is playing, so I cannot adjust volume."
        current = playback.get("device", {}).get("volume_percent")
        if current is None:
            return "Could not read current volume."
        target = max(0, min(100, current + delta))
        return self.set_volume(target)
