package ytmusic

import (
	"encoding/json"
	"os"
	"path/filepath"
	"time"

	"github.com/bjarneo/cliamp/internal/appdir"
	"github.com/bjarneo/cliamp/playlist"
)

// cacheTTL is how long cached playlist/track data is considered fresh.
// After this, data is refetched from the API on next access.
const cacheTTL = 24 * time.Hour

// ytCache stores playlists and tracks on disk for fast startup.
// Path: ~/.config/cliamp/ytmusic_cache.json
type ytCache struct {
	Playlists   []playlistEntry            `json:"playlists,omitempty"`
	PlaylistsAt time.Time                  `json:"playlists_at"`
	Tracks      map[string]cachedTrackList `json:"tracks,omitempty"`
}

type cachedTrackList struct {
	Items     []playlist.Track `json:"items"`
	FetchedAt time.Time        `json:"fetched_at"`
}

func ytCachePath() string {
	dir, err := appdir.Dir()
	if err != nil {
		return ""
	}
	return filepath.Join(dir, "ytmusic_cache.json")
}

func newYTCache() *ytCache {
	return &ytCache{Tracks: make(map[string]cachedTrackList)}
}

func loadYTCache() *ytCache {
	data, err := os.ReadFile(ytCachePath())
	if err != nil {
		return newYTCache()
	}
	var c ytCache
	if err := json.Unmarshal(data, &c); err != nil {
		return newYTCache()
	}
	if c.Tracks == nil {
		c.Tracks = make(map[string]cachedTrackList)
	}
	return &c
}

// snapshot returns a JSON-encoded copy of the cache. Call under the mutex
// so json.Marshal doesn't race with concurrent setPlaylists/setTracks calls.
func (c *ytCache) snapshot() []byte {
	data, err := json.Marshal(c)
	if err != nil {
		return nil
	}
	return data
}

// save writes previously-snapshotted data to disk. Safe to call without a lock.
func saveSnapshot(data []byte) {
	if data == nil {
		return
	}
	path := ytCachePath()
	if path == "" {
		return
	}
	os.MkdirAll(filepath.Dir(path), 0o700)
	os.WriteFile(path, data, 0o600)
}

func (c *ytCache) playlistsFresh() bool {
	return len(c.Playlists) > 0 && time.Since(c.PlaylistsAt) < cacheTTL
}

func (c *ytCache) tracksFresh(playlistID string) ([]playlist.Track, bool) {
	ct, ok := c.Tracks[playlistID]
	if !ok || len(ct.Items) == 0 {
		return nil, false
	}
	if time.Since(ct.FetchedAt) >= cacheTTL {
		return nil, false
	}
	return ct.Items, true
}

func (c *ytCache) setPlaylists(pl []playlistEntry) {
	c.Playlists = pl
	c.PlaylistsAt = time.Now()
}

func (c *ytCache) setTracks(playlistID string, tracks []playlist.Track) {
	c.Tracks[playlistID] = cachedTrackList{
		Items:     tracks,
		FetchedAt: time.Now(),
	}
}

func (c *ytCache) clear() {
	c.Playlists = nil
	c.PlaylistsAt = time.Time{}
	c.Tracks = make(map[string]cachedTrackList)
}
