package data

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

	"github.com/gmgauthier/gostations/internal/radio"
)

// Favorites manages a user's preferred stations persisted as JSON.
// Keyed by URL for deduplication (stable enough; can enhance with UUID later).
type Favorites struct {
	mu       sync.RWMutex
	path     string
	stations map[string]radio.Station // url -> station
	dirty    bool
}

// NewFavorites creates or loads favorites from the standard location under XDG config.
// Falls back gracefully; errors only on unrecoverable write issues later.
func NewFavorites() (*Favorites, error) {
	f := &Favorites{
		stations: make(map[string]radio.Station),
	}

	configDir, err := os.UserConfigDir()
	if err != nil {
		configDir = filepath.Join(os.TempDir(), "gostations-fallback")
	}
	f.path = filepath.Join(configDir, "gostations", "favorites.json")

	// Ensure dir
	if err := os.MkdirAll(filepath.Dir(f.path), 0750); err != nil {
		return nil, err
	}

	if err := f.load(); err != nil {
		// If load fails (corrupt etc), start empty but don't fail hard
		// Caller can log warning.
		f.stations = make(map[string]radio.Station)
	}

	return f, nil
}

func (f *Favorites) load() error {
	data, err := os.ReadFile(f.path)
	if os.IsNotExist(err) {
		return nil // fresh
	}
	if err != nil {
		return err
	}
	if len(data) == 0 {
		return nil
	}

	var list []radio.Station
	if err := json.Unmarshal(data, &list); err != nil {
		return err
	}

	f.mu.Lock()
	defer f.mu.Unlock()
	f.stations = make(map[string]radio.Station, len(list))
	for _, s := range list {
		if s.Url != "" {
			f.stations[s.Url] = s
		}
	}
	return nil
}

// Save persists if dirty.
func (f *Favorites) Save() error {
	f.mu.RLock()
	defer f.mu.RUnlock()

	if !f.dirty {
		return nil
	}

	list := make([]radio.Station, 0, len(f.stations))
	for _, s := range f.stations {
		list = append(list, s)
	}

	// Pretty for humans
	b, err := json.MarshalIndent(list, "", "  ")
	if err != nil {
		return err
	}

	tmp := f.path + ".tmp"
	if err := os.WriteFile(tmp, b, 0644); err != nil {
		return err
	}
	if err := os.Rename(tmp, f.path); err != nil {
		_ = os.Remove(tmp)
		return err
	}

	f.dirty = false
	return nil
}

// Add inserts or updates a station (by URL). Marks dirty.
func (f *Favorites) Add(s radio.Station) {
	if s.Url == "" {
		return
	}
	f.mu.Lock()
	defer f.mu.Unlock()
	f.stations[s.Url] = s
	f.dirty = true
}

// Remove by URL.
func (f *Favorites) Remove(url string) {
	f.mu.Lock()
	defer f.mu.Unlock()
	if _, ok := f.stations[url]; ok {
		delete(f.stations, url)
		f.dirty = true
	}
}

// Contains reports if a station (by URL) is favorited.
func (f *Favorites) Contains(url string) bool {
	f.mu.RLock()
	defer f.mu.RUnlock()
	_, ok := f.stations[url]
	return ok
}

// List returns a copy of all favorited stations (order not guaranteed; caller can sort).
func (f *Favorites) List() []radio.Station {
	f.mu.RLock()
	defer f.mu.RUnlock()
	out := make([]radio.Station, 0, len(f.stations))
	for _, s := range f.stations {
		out = append(out, s)
	}
	return out
}

// Path returns the JSON file location (for diagnostics / "config path" style cmds).
func (f *Favorites) Path() string {
	return f.path
}
