package callee

import (
	"context"
	"time"

	"code.justin.tv/sse/malachai/pkg/capabilities"
	"code.justin.tv/sse/malachai/pkg/internal/closer"
	"code.justin.tv/sse/malachai/pkg/log"
)

const (
	defaultCapabilitiesTTL = 24 * time.Hour
)

type calleeCapabilitiesClient struct {
	cache         *calleeCapabilitiesCache
	capabilities  capabilities.Manager
	invalidations chan struct{}
	log           log.S2SLogger

	cfg *calleeCapabilitiesConfig

	closer closer.API
}

func newCalleeCapabilitiesClient(cfg *Config, logger log.S2SLogger, invalidations chan struct{}, closer closer.API) (capClient *calleeCapabilitiesClient, err error) {
	capabilitiesClient, err := capabilities.New(cfg.CapabilitiesConfig)
	if err != nil {
		return
	}

	capClient = &calleeCapabilitiesClient{
		cache:         newCalleeCapabilitiesCache(cfg.calleeCapConfig.capabilitiesTTL),
		capabilities:  capabilitiesClient,
		invalidations: invalidations,
		log:           logger,
		cfg:           cfg.calleeCapConfig,
		closer:        closer,
	}
	return
}

type calleeCapabilitiesConfig struct {
	callee          string
	ttl             time.Duration
	capabilitiesTTL time.Duration
	roleArn         string
}

// FillDefaults fills default values
func (cfg *calleeCapabilitiesConfig) FillDefaults() {
	if cfg.ttl == 0 {
		cfg.ttl = 1 * time.Hour
	}

	if cfg.capabilitiesTTL == 0 {
		cfg.capabilitiesTTL = defaultCapabilitiesTTL
	}
}

func (ccc *calleeCapabilitiesClient) get(caller string) (c *capabilities.Capabilities, err error) {
	return ccc.cache.Get(caller)
}

func (ccc *calleeCapabilitiesClient) updateCache() (err error) {
	ctx, cancel := context.WithCancel(ccc.closer)
	defer cancel()
	caps, err := ccc.capabilities.GetForCallee(ctx, ccc.cfg.callee)
	if err != nil {
		return
	}

	updated := make(map[string]*capabilities.Capabilities)
	for _, c := range caps {
		updated[c.Caller] = c
	}

	ccc.cache.Update(updated)

	return
}

func (ccc *calleeCapabilitiesClient) start() {
	timer := time.NewTimer(ccc.cfg.ttl)
	for {
		err := ccc.updateCache()
		if err != nil {
			ccc.log.Errorf("error while updating capability cache: %s", err.Error())
		}

		select {
		case <-ccc.closer.Done():
			ccc.log.Debug("exiting due to closed client")
			return
		case <-ccc.invalidations:
			ccc.log.Debug("updating cache due to invalidation")
		case <-timer.C:
			timer.Reset(ccc.cfg.ttl)
			ccc.log.Debug("updating cache due to invalidation timeout")
		}
	}
}
