"""Abstract base classes for pushtunes services."""

from abc import ABC, abstractmethod
from dataclasses import asdict

from pushtunes.models.album import Album
from pushtunes.models.track import Track
import os
import time
import orjson
from platformdirs import PlatformDirs

from pushtunes.utils.logging import get_logger


class MusicService(ABC):
    """Base class for music services (e.g., Spotify, YouTube Music)."""

    log = get_logger()

    def __init__(self):
        self.dirs: PlatformDirs = PlatformDirs(appname="pushtunes", appauthor="psy-q")
        self.album_cache_file = os.path.join(
            self.dirs.user_cache_dir, f"{self.service_name}_albums.json"
        )
        self.track_cache_file = os.path.join(
            self.dirs.user_cache_dir, f"{self.service_name}_tracks.json"
        )

    @abstractmethod
    def search_albums(self, album: Album) -> list[Album]:
        pass

    @abstractmethod
    def is_album_in_library(self, album: Album) -> bool:
        pass

    @abstractmethod
    def add_album(self, album: Album) -> bool:
        pass

    @abstractmethod
    def search_tracks(self, track: Track) -> list[Track]:
        pass

    @abstractmethod
    def is_track_in_library(self, track: Track) -> bool:
        pass

    @abstractmethod
    def add_track(self, track: Track) -> bool:
        pass

    def _update_album_cache(self):
        if self._is_album_cache_expired():
            self.log.info("Album cache is expired or missing, fetching library albums")
            try:
                _ = self.get_library_albums()
                # Only save cache if get_library_albums() succeeded
                if len(self.albums) > 0:
                    self._save_album_cache()
                    self.log.debug(f"Saved {len(self.albums)} albums to cache")
                else:
                    self.log.warning(
                        "get_library_albums() returned 0 albums. Not saving cache. "
                        "This could indicate an authentication issue."
                    )
            except Exception as e:
                self.log.error(
                    f"Failed to fetch library albums: {e}. Not saving cache."
                )
                # Re-raise so caller knows the operation failed
                raise

    def _save_album_cache(self):
        with open(self.album_cache_file, "w+") as cache:
            albums_as_dicts = [asdict(album) for album in self.albums]
            _ = cache.write(
                orjson.dumps(albums_as_dicts, option=orjson.OPT_INDENT_2).decode()
            )

    def _load_album_cache(self):
        if self._is_album_cache_expired():
            try:
                self._update_album_cache()
            except Exception as e:
                self.log.error(f"Failed to update album cache: {e}")
                # Don't try to load from file if update failed - propagate the error
                raise
        try:
            with open(self.album_cache_file, "r") as cache:
                albums = [Album(**a) for a in orjson.loads(cache.read())]
                self.albums = albums
                self.log.debug(f"Loaded {len(self.albums)} albums from cache")
        except FileNotFoundError:
            self.log.error(
                f"Album cache file not found. Please ensure authentication is set up correctly."
            )
            raise
        except Exception as e:
            self.log.error(f"Error loading album cache: {e}")
            raise

    def _is_album_cache_expired(self, seconds: int = 3600):
        if not os.path.exists(self.album_cache_file):
            return True
        else:
            age = time.time() - os.path.getmtime(self.album_cache_file)
            return age > seconds

    def _update_track_cache(self):
        if self._is_track_cache_expired():
            self.log.info("Track cache is expired or missing, fetching library tracks")
            try:
                _ = self.get_library_tracks()
                # Only save cache if get_library_tracks() succeeded
                if len(self.tracks) > 0:
                    self._save_track_cache()
                    self.log.debug(f"Saved {len(self.tracks)} tracks to cache")
                else:
                    self.log.warning(
                        "get_library_tracks() returned 0 tracks. Not saving cache. "
                        "This could indicate an authentication issue."
                    )
            except Exception as e:
                self.log.error(
                    f"Failed to fetch library tracks: {e}. Not saving cache."
                )
                # Re-raise so caller knows the operation failed
                raise

    def _save_track_cache(self):
        with open(self.track_cache_file, "w+") as cache:
            tracks_as_dicts = [asdict(track) for track in self.tracks]
            _ = cache.write(
                orjson.dumps(tracks_as_dicts, option=orjson.OPT_INDENT_2).decode()
            )

    def _load_track_cache(self):
        if self._is_track_cache_expired():
            try:
                self._update_track_cache()
            except Exception as e:
                self.log.error(f"Failed to update track cache: {e}")
                # Don't try to load from file if update failed - propagate the error
                raise
        try:
            with open(self.track_cache_file, "r") as cache:
                tracks = [Track(**t) for t in orjson.loads(cache.read())]
                self.tracks = tracks
                self.log.debug(f"Loaded {len(self.tracks)} tracks from cache")
        except FileNotFoundError:
            self.log.error(
                f"Track cache file not found. Please ensure authentication is set up correctly."
            )
            raise
        except Exception as e:
            self.log.error(f"Error loading track cache: {e}")
            raise

    def _is_track_cache_expired(self, seconds: int = 3600):
        if not os.path.exists(self.track_cache_file):
            return True
        else:
            age = time.time() - os.path.getmtime(self.track_cache_file)
            return age > seconds
