package setting

import (
	"encoding/json"
	"fmt"
	"sync"
	"time"

	"github.com/yaoapp/gou/store"
)

// Global is the singleton Setting Registry.
var Global *Registry

// Registry manages namespaced settings with three-level scope cascade.
type Registry struct {
	store store.Store
	cache store.Store
	mu    sync.RWMutex
}

// Init initializes the global Registry.
// Must be called after store.Load (so __yao.store and __yao.cache are available).
func Init() error {
	s, err := store.Get("__yao.store")
	if err != nil {
		return fmt.Errorf("setting.Init: %w", err)
	}
	c, _ := store.Get("__yao.cache")
	Global = &Registry{store: s, cache: c}
	return nil
}

// Get reads the raw data for a single scope+namespace.
// If one or more dest pointers are provided, the data is also unmarshalled
// into dest[0] (like json.Unmarshal).
func (r *Registry) Get(scope ScopeID, ns string, dest ...interface{}) (map[string]interface{}, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	data, err := storeGet(r.store, r.cache, scope, ns)
	if err != nil {
		return nil, err
	}

	if len(dest) > 0 && dest[0] != nil {
		if err := bindDest(data, dest[0]); err != nil {
			return data, fmt.Errorf("setting bind: %w", err)
		}
	}
	return data, nil
}

// GetMerged reads a namespace across all three scopes and returns a shallow-merged
// result: system <- team <- user (later wins).
// If one or more dest pointers are provided, the merged data is also unmarshalled
// into dest[0].
func (r *Registry) GetMerged(userID, teamID, ns string, dest ...interface{}) (map[string]interface{}, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()

	merged := make(map[string]interface{})

	if sys, err := storeGet(r.store, r.cache, ScopeID{Scope: ScopeSystem}, ns); err == nil {
		shallowMerge(merged, sys)
	}

	if teamID != "" {
		if team, err := storeGet(r.store, r.cache, ScopeID{Scope: ScopeTeam, TeamID: teamID}, ns); err == nil {
			shallowMerge(merged, team)
		}
	}

	if userID != "" {
		if user, err := storeGet(r.store, r.cache, ScopeID{Scope: ScopeUser, UserID: userID}, ns); err == nil {
			shallowMerge(merged, user)
		}
	}

	if len(merged) == 0 {
		return nil, fmt.Errorf("setting %s: no data found at any scope", ns)
	}

	if len(dest) > 0 && dest[0] != nil {
		if err := bindDest(merged, dest[0]); err != nil {
			return merged, fmt.Errorf("setting bind: %w", err)
		}
	}
	return merged, nil
}

// Set writes (or overwrites) a namespace entry for the given scope.
func (r *Registry) Set(scope ScopeID, ns string, data map[string]interface{}) (*Entry, error) {
	r.mu.Lock()
	defer r.mu.Unlock()

	if ns == "" {
		return nil, fmt.Errorf("namespace is required")
	}

	if err := storeSet(r.store, r.cache, scope, ns, data); err != nil {
		return nil, err
	}
	if err := indexAdd(r.store, r.cache, scope, ns); err != nil {
		return nil, err
	}

	return &Entry{
		Namespace: ns,
		Scope:     scope,
		Data:      data,
		UpdatedAt: time.Now().UTC().Format(time.RFC3339),
	}, nil
}

// Delete removes a namespace entry from a given scope.
func (r *Registry) Delete(scope ScopeID, ns string) error {
	r.mu.Lock()
	defer r.mu.Unlock()

	sk := storeKey(scope, ns)
	if !r.store.Has(sk) {
		return fmt.Errorf("setting %s/%s not found", scopePrefix(scope), ns)
	}

	if err := storeDel(r.store, r.cache, scope, ns); err != nil {
		return err
	}
	return indexRemove(r.store, r.cache, scope, ns)
}

// ListNamespaces returns all namespace names stored under the given scope.
func (r *Registry) ListNamespaces(scope ScopeID) ([]string, error) {
	r.mu.RLock()
	defer r.mu.RUnlock()
	return indexGet(r.store, r.cache, scope)
}

// Reload clears the cache and re-populates it from the persistent store.
func (r *Registry) Reload() error {
	r.mu.Lock()
	defer r.mu.Unlock()

	if r.cache != nil {
		_ = r.cache.Del(keyPrefix + "*")
	}

	for _, scope := range []ScopeID{
		{Scope: ScopeSystem},
	} {
		keys, err := indexGet(r.store, nil, scope)
		if err != nil {
			continue
		}
		ik := indexKey(scope)
		raw, ok := r.store.Get(ik)
		if ok && r.cache != nil {
			r.cache.Set(ik, raw, 0)
		}
		for _, ns := range keys {
			if data, err := storeGet(r.store, nil, scope, ns); err == nil && r.cache != nil {
				r.cache.Set(storeKey(scope, ns), data, 0)
			}
		}
	}
	return nil
}

// shallowMerge copies all keys from src into dst (overwrites existing keys).
func shallowMerge(dst, src map[string]interface{}) {
	for k, v := range src {
		dst[k] = v
	}
}

// bindDest marshals data to JSON and then unmarshals into the dest pointer.
func bindDest(data map[string]interface{}, dest interface{}) error {
	raw, err := json.Marshal(data)
	if err != nil {
		return err
	}
	return json.Unmarshal(raw, dest)
}
