package meta

import (
	"fmt"

	"zotregistry.dev/zot/errors"
	"zotregistry.dev/zot/pkg/api/config"
	rediscfg "zotregistry.dev/zot/pkg/api/config/redis"
	"zotregistry.dev/zot/pkg/log"
	"zotregistry.dev/zot/pkg/meta/boltdb"
	mdynamodb "zotregistry.dev/zot/pkg/meta/dynamodb"
	"zotregistry.dev/zot/pkg/meta/redis"
	mTypes "zotregistry.dev/zot/pkg/meta/types"
	sconstants "zotregistry.dev/zot/pkg/storage/constants"
)

func New(storageConfig config.StorageConfig, log log.Logger) (mTypes.MetaDB, error) {
	if storageConfig.RemoteCache {
		if storageConfig.CacheDriver["name"] == sconstants.DynamoDBDriverName {
			dynamoParams := getDynamoParams(storageConfig.CacheDriver, log)

			client, err := mdynamodb.GetDynamoClient(dynamoParams)
			if err != nil {
				return nil, err
			}

			return mdynamodb.New(client, dynamoParams, log) //nolint:contextcheck
		}

		if storageConfig.CacheDriver["name"] == sconstants.RedisDriverName {
			redisParams := getRedisParams(storageConfig.CacheDriver, log)

			client, err := rediscfg.GetRedisClient(storageConfig.CacheDriver, log)
			if err != nil { //nolint:wsl
				return nil, err
			}

			return redis.New(client, redisParams, log) //nolint:contextcheck
		}

		// this behavior is also mentioned in the configuration validation logic inside the cli package
		return nil, fmt.Errorf("%w: cachedriver %s and remotecache %t", errors.ErrBadConfig,
			storageConfig.CacheDriver["name"], storageConfig.RemoteCache)
	}

	if driverName, ok := storageConfig.CacheDriver["name"]; ok && driverName != sconstants.BoltdbName {
		// this behavior is also mentioned in the configuration validation logic inside the cli package
		log.Warn().Interface("cachedriver", driverName).Bool("remotecache", storageConfig.RemoteCache).
			Msg("unsupported cachedriver for remotecache disabled, will default to boltdb")
	}

	params := boltdb.DBParameters{}
	params.RootDir = storageConfig.RootDirectory

	driver, err := boltdb.GetBoltDriver(params)
	if err != nil {
		return nil, err
	}

	return boltdb.New(driver, log) //nolint:contextcheck
}

func getDynamoParams(cacheDriverConfig map[string]interface{}, log log.Logger) mdynamodb.DBDriverParameters {
	allParametersOk := true

	endpoint, ok := toStringIfOk(cacheDriverConfig, "endpoint", "", log)
	allParametersOk = allParametersOk && ok

	region, ok := toStringIfOk(cacheDriverConfig, "region", "", log)
	allParametersOk = allParametersOk && ok

	repoMetaTablename, ok := toStringIfOk(cacheDriverConfig, "repometatablename", "", log)
	allParametersOk = allParametersOk && ok

	repoBlobsInfoTablename, ok := toStringIfOk(cacheDriverConfig, "repoblobsinfotablename", "", log)
	allParametersOk = allParametersOk && ok

	imageMetaTablename, ok := toStringIfOk(cacheDriverConfig, "imagemetatablename", "", log)
	allParametersOk = allParametersOk && ok

	apiKeyTablename, ok := toStringIfOk(cacheDriverConfig, "apikeytablename", "", log)
	allParametersOk = allParametersOk && ok

	versionTablename, ok := toStringIfOk(cacheDriverConfig, "versiontablename", "", log)
	allParametersOk = allParametersOk && ok

	userDataTablename, ok := toStringIfOk(cacheDriverConfig, "userdatatablename", "", log)
	allParametersOk = allParametersOk && ok

	if !allParametersOk {
		log.Panic().Msg("dynamo parameters are not specified correctly, can't proceed")
	}

	return mdynamodb.DBDriverParameters{
		Endpoint:               endpoint,
		Region:                 region,
		RepoMetaTablename:      repoMetaTablename,
		RepoBlobsInfoTablename: repoBlobsInfoTablename,
		ImageMetaTablename:     imageMetaTablename,
		UserDataTablename:      userDataTablename,
		APIKeyTablename:        apiKeyTablename,
		VersionTablename:       versionTablename,
	}
}

func getRedisParams(cacheDriverConfig map[string]interface{}, log log.Logger) redis.DBDriverParameters {
	keyPrefix, ok := toStringIfOk(cacheDriverConfig, "keyprefix", "zot", log)
	if !ok {
		log.Panic().Msg("redis parameters are not specified correctly, can't proceed")
	}

	return redis.DBDriverParameters{
		KeyPrefix: keyPrefix,
	}
}

func toStringIfOk(cacheDriverConfig map[string]interface{},
	param string,
	defaultVal string,
	log log.Logger,
) (string, bool) {
	val, ok := cacheDriverConfig[param]

	if !ok && defaultVal != "" {
		log.Info().Str("field", param).Str("default", defaultVal).
			Msg("field is not present in CacheDriver config, using default value")

		return defaultVal, true
	} else if !ok {
		log.Error().Str("field", param).Msg("failed to parse CacheDriver config, field is not present")

		return "", false
	}

	str, ok := val.(string)
	if !ok {
		log.Error().Str("parameter", param).Msg("failed to parse CacheDriver config, parameter isn't a string")

		return "", false
	}

	if str == "" {
		log.Error().Str("field", param).Msg("failed to parse CacheDriver config, field is empty")

		return "", false
	}

	return str, true
}

func Close(metadb mTypes.MetaDB) error {
	return metadb.Close()
}
