package admin

import (
	"context"
	"time"

	"forge.cadoles.com/cadoles/bouncer/internal/config"
	"forge.cadoles.com/cadoles/bouncer/internal/lock/redis"
	"forge.cadoles.com/cadoles/bouncer/internal/schema"
	"forge.cadoles.com/cadoles/bouncer/internal/setup"
	"forge.cadoles.com/cadoles/bouncer/internal/store"
	"github.com/pkg/errors"
	"gitlab.com/wpetit/goweb/logger"
)

func (s *Server) bootstrapProxies(ctx context.Context) error {
	if err := s.validateBootstrap(ctx); err != nil {
		return errors.Wrap(err, "could not validate bootstrapped proxies")
	}

	proxyRepo := s.proxyRepository
	layerRepo := s.layerRepository

	lockTimeout := time.Duration(s.bootstrapConfig.LockTimeout)
	locker := redis.NewLocker(s.redisClient, int(s.bootstrapConfig.MaxConnectionRetries))

	err := locker.WithLock(ctx, "bouncer-admin-bootstrap", lockTimeout, func(ctx context.Context) error {
		logger.Info(ctx, "bootstrapping proxies")

		for proxyName, proxyConfig := range s.bootstrapConfig.Proxies {
			loopCtx := logger.With(ctx, logger.F("proxyName", proxyName), logger.F("proxyFrom", proxyConfig.From), logger.F("proxyTo", proxyConfig.To))

			_, err := s.proxyRepository.GetProxy(ctx, proxyName)
			if !errors.Is(err, store.ErrNotFound) {
				if err != nil {
					return errors.WithStack(err)
				}

				if proxyConfig.Recreate {
					logger.Info(loopCtx, "force recreating proxy")

					if err := s.deleteProxyAndLayers(ctx, proxyName); err != nil {
						return errors.WithStack(err)
					}
				} else {
					logger.Info(loopCtx, "ignoring existing proxy")
					continue
				}
			}

			logger.Info(loopCtx, "creating proxy")

			if _, err := proxyRepo.CreateProxy(ctx, proxyName, string(proxyConfig.To), proxyConfig.From...); err != nil {
				return errors.WithStack(err)
			}

			_, err = proxyRepo.UpdateProxy(
				ctx, proxyName,
				store.WithProxyUpdateEnabled(bool(proxyConfig.Enabled)),
				store.WithProxyUpdateWeight(int(proxyConfig.Weight)),
			)
			if err != nil {
				return errors.WithStack(err)
			}

			for layerName, layerConfig := range proxyConfig.Layers {
				layerType := store.LayerType(layerConfig.Type)
				layerOptions := store.LayerOptions(layerConfig.Options.Data)

				if _, err := layerRepo.CreateLayer(ctx, proxyName, layerName, layerType, layerOptions); err != nil {
					return errors.WithStack(err)
				}

				_, err := layerRepo.UpdateLayer(
					ctx,
					proxyName, layerName,
					store.WithLayerUpdateEnabled(bool(layerConfig.Enabled)),
					store.WithLayerUpdateOptions(layerOptions),
					store.WithLayerUpdateWeight(int(layerConfig.Weight)),
				)
				if err != nil {
					return errors.WithStack(err)
				}
			}
		}

		return nil
	})
	if err != nil {
		return errors.WithStack(err)
	}

	return nil
}

const validateErrMessage = "could not validate proxy '%s': could not validate layer '%s'"

func (s *Server) validateBootstrap(ctx context.Context) error {
	for proxyName, proxyConf := range s.bootstrapConfig.Proxies {
		for layerName, layerConf := range proxyConf.Layers {
			layerType := store.LayerType(layerConf.Type)
			if !setup.LayerTypeExists(layerType) {
				return errors.Errorf(validateErrMessage+": could not find layer type '%s'", proxyName, layerName, layerType)
			}

			layerOptionsSchema, err := setup.GetLayerOptionsSchema(layerType)
			if err != nil {
				return errors.Wrapf(err, validateErrMessage, proxyName, layerName)
			}

			rawOptions := func(opts config.InterpolatedMap) map[string]any {
				return opts.Data
			}(layerConf.Options)

			if err := schema.Validate(ctx, layerOptionsSchema, rawOptions); err != nil {
				return errors.Wrapf(err, validateErrMessage, proxyName, layerName)
			}
		}
	}

	return nil
}