package zonecache

import (
	"errors"
	"sync"
)

func New[Zone any](fetchAll func() (map[string]Zone, error)) ZoneCache[Zone] {
	return ZoneCache[Zone]{fetchAll: fetchAll}
}

var ErrZoneNotFound = errors.New("zone not found")

type ZoneCache[Zone any] struct {
	mu       sync.Mutex
	cached   bool
	cache    map[string]Zone
	fetchAll func() (map[string]Zone, error)
}

func (c *ZoneCache[Zone]) ensureCached() error {
	if c.cached {
		return nil
	}
	zones, err := c.fetchAll()
	if err != nil {
		return err
	}
	if c.cache == nil {
		c.cache = make(map[string]Zone, len(zones))
	}
	for name, z := range zones {
		c.cache[name] = z
	}
	c.cached = true
	return nil
}

func (c *ZoneCache[Zone]) HasZone(name string) (bool, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if err := c.ensureCached(); err != nil {
		return false, err
	}
	_, ok := c.cache[name]
	return ok, nil
}

func (c *ZoneCache[Zone]) GetZone(name string) (Zone, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if err := c.ensureCached(); err != nil {
		var z Zone
		return z, err
	}
	z, ok := c.cache[name]
	if !ok {
		return z, ErrZoneNotFound
	}
	return z, nil
}

func (c *ZoneCache[Zone]) GetZoneNames() ([]string, error) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if err := c.ensureCached(); err != nil {
		return nil, err
	}
	names := make([]string, 0, len(c.cache))
	for name := range c.cache {
		names = append(names, name)
	}
	return names, nil
}

func (c *ZoneCache[Zone]) SetZone(name string, z Zone) {
	c.mu.Lock()
	defer c.mu.Unlock()

	if c.cache == nil {
		c.cache = make(map[string]Zone, 1)
	}
	c.cache[name] = z
}
