package cache

import (
	"encoding/json"
	"github.com/fiskerinc/cloud-services/pkg/common"
	orm "github.com/fiskerinc/cloud-services/pkg/db/queries"
	"fmt"
	"github.com/ReneKroon/ttlcache/v2"
	"github.com/pkg/errors"
	"sync"
	"time"
)

var (
	ErrCacheNotInitialized = errors.New("cache is not initialized")
)

type VehicleCacher interface {
	Set(key VehiclesTTLParams, value *VehiclesTTLResult) error
	Get(key VehiclesTTLParams) (*VehiclesTTLResult, error)
}

type VehiclesCache struct {
	duration  time.Duration
	limit     int
	cache     *ttlcache.Cache
	onceCache *sync.Once
}

func (c *VehiclesCache) Duration() time.Duration {
	return c.duration
}

func (c *VehiclesCache) Limit() int {
	return c.limit
}

func (c *VehiclesCache) Once() *sync.Once {
	return c.onceCache
}

func (c *VehiclesCache) SetCache(cache *ttlcache.Cache) {
	c.cache = cache
}

func (c *VehiclesCache) Cache() *ttlcache.Cache {
	return c.cache
}

type VehiclesTTLParams struct {
	Options         orm.PageQueryOptions `json:"options"`
	CarOnlineFilter *common.CarOnlineFilter   `json:"car_online_filter"`
	Search          string               `json:"search"`
}

type VehiclesTTLResult struct {
	Data  []common.Car `json:"data"`
	Total int     `json:"total"`
}

func NewVehiclesCache(duration time.Duration, limit int) (*VehiclesCache, error) {
	c := &VehiclesCache{
		duration:  duration,
		limit:     limit,
		onceCache: &sync.Once{},
	}

	if cache := logCache(c); cache == nil {
		return nil, ErrCacheNotInitialized
	}

	return c, nil
}
func (c *VehiclesCache) Set(key VehiclesTTLParams, value *VehiclesTTLResult) error {
	if cache := logCache(c); cache == nil {
		return ErrCacheNotInitialized
	}

	keyBts, err := json.Marshal(key)
	if err != nil {
		return errors.WithMessagef(err, "failed to marshal key")
	}

	return c.cache.Set(string(keyBts), value)
}

func (c *VehiclesCache) Get(key VehiclesTTLParams) (*VehiclesTTLResult, error) {
	if cache := logCache(c); cache == nil {
		return nil, ErrCacheNotInitialized
	}

	keyBts, err := json.Marshal(key)
	if err != nil {
		return nil, errors.WithMessagef(err, "failed to marshal key")
	}

	value, err := c.cache.Get(string(keyBts))
	if err != nil {
		return nil, fmt.Errorf("failed to get value from cache: %w", err)
	}

	return value.(*VehiclesTTLResult), nil
}

type Cacher interface {
	Duration() time.Duration
	Limit() int
	Once() *sync.Once
	Cache() *ttlcache.Cache
	SetCache(*ttlcache.Cache)
}

func logCache(cacher Cacher) *ttlcache.Cache {
	cacher.Once().Do(func() {
		if cacher.Cache() == nil {
			cache := ttlcache.NewCache()
			cache.SetTTL(cacher.Duration())
			cache.SetCacheSizeLimit(cacher.Limit())
			cacher.SetCache(cache)
		}
	})

	return cacher.Cache()
}
