//go:generate eventbus-mockery -dir . -output ../mocks -case snake -name "Client"
package servicecatalog

import (
	"context"
	"fmt"
	"net/http"

	"code.justin.tv/eventbus/controlplane/internal/environment"
	"code.justin.tv/eventbus/controlplane/internal/logger"

	"github.com/karlseguin/ccache"
	"github.com/pkg/errors"
	"go.uber.org/zap"
)

const (
	missingServiceWarn = "Unable to retrieve info from service catalog"
)

var ErrDoesNotExist = errors.New("service not found in service catalog")

type Client interface {
	Get(ctx context.Context, serviceCatalogID string) (*Response, error)
}

func New() *ServiceCatalog {
	var tr = (http.DefaultTransport.(*http.Transport)).Clone()
	tr.MaxConnsPerHost = 20

	s := &ServiceCatalog{
		fetcher: &ServiceCatalogFetcher{
			httpClient: &http.Client{Transport: tr},
		},
		cache: ccache.New(ccache.Configure().MaxSize(cacheMaxSize).ItemsToPrune(cacheItemsToPrune)),
		done:  make(chan interface{}),
	}
	go s.poll()

	return s
}

type ServiceCatalog struct {
	fetcher Fetcher
	cache   *ccache.Cache
	done    chan interface{}
}

func (s *ServiceCatalog) Get(ctx context.Context, serviceCatalogID string) (*Response, error) {
	if environment.IsEndToEndTest() {
		return &Response{
			Name:        fmt.Sprintf("E2E-%s", serviceCatalogID),
			Description: "E2E Service",
		}, nil
	} else if environment.IsLocalDev() {
		return generateLocalServiceData(), nil
	}

	return s.getNonLocal(ctx, serviceCatalogID)
}

func (s *ServiceCatalog) getNonLocal(ctx context.Context, serviceCatalogID string) (*Response, error) {
	log := logger.FromContext(ctx)
	var item *ccache.Item
	item = s.cache.Get(serviceCatalogID)
	if item == nil {
		// If a service has just been added to the service catalog it may not yet be in our cache. We check against this by repopulating the cache.
		err := s.populateCache(ctx)
		if err != nil {
			return nil, errors.Wrap(err, "could not refresh service catalog cache")
		}
		item = s.cache.Get(serviceCatalogID)
		if item == nil {
			s.cacheMissingService(serviceCatalogID)

			log.Warn(missingServiceWarn, zap.String("serviceCatalogID", serviceCatalogID))
			return nil, ErrDoesNotExist
		}
	}

	switch v := item.Value().(type) {
	case *NonExistentService:
		return nil, ErrDoesNotExist
	case *Response:
		return v, nil
	default:
		log.Error("unexpected item in cache", zap.Any("type", v), zap.Any("item", item))
		return nil, errors.Errorf("unexpected item in cache %+v", item)
	}

}
