package session

import (
	"crypto/rand"
	"crypto/tls"
	"crypto/x509"
	"fmt"
	"strings"

	"github.com/fasthttp/session/v2"
	"github.com/fasthttp/session/v2/providers/redis"
	"github.com/valyala/fasthttp"

	"github.com/authelia/authelia/v4/internal/configuration/schema"
	"github.com/authelia/authelia/v4/internal/logging"
	"github.com/authelia/authelia/v4/internal/utils"
)

// NewProviderConfig creates a configuration for creating the session provider.
func NewProviderConfig(configuration schema.SessionConfiguration, certPool *x509.CertPool) ProviderConfig {
	config := session.NewDefaultConfig()

	config.SessionIDGeneratorFunc = func() []byte {
		bytes := make([]byte, 32)

		_, _ = rand.Read(bytes)

		for i, b := range bytes {
			bytes[i] = randomSessionChars[b%byte(len(randomSessionChars))]
		}

		return bytes
	}

	// Override the cookie name.
	config.CookieName = configuration.Name

	// Set the cookie to the given domain.
	config.Domain = configuration.Domain

	// Set the cookie SameSite option.
	switch configuration.SameSite {
	case "strict":
		config.CookieSameSite = fasthttp.CookieSameSiteStrictMode
	case "none":
		config.CookieSameSite = fasthttp.CookieSameSiteNoneMode
	case "lax":
		config.CookieSameSite = fasthttp.CookieSameSiteLaxMode
	default:
		config.CookieSameSite = fasthttp.CookieSameSiteLaxMode
	}

	// Only serve the header over HTTPS.
	config.Secure = true

	// Ignore the error as it will be handled by validator.
	config.Expiration, _ = utils.ParseDurationString(configuration.Expiration)

	config.IsSecureFunc = func(*fasthttp.RequestCtx) bool {
		return true
	}

	var redisConfig *redis.Config

	var redisSentinelConfig *redis.FailoverConfig

	var providerName string

	// If redis configuration is provided, then use the redis provider.
	switch {
	case configuration.Redis != nil:
		serializer := NewEncryptingSerializer(configuration.Secret)

		var tlsConfig *tls.Config

		if configuration.Redis.TLS != nil {
			tlsConfig = utils.NewTLSConfig(configuration.Redis.TLS, tls.VersionTLS12, certPool)
		}

		if configuration.Redis.HighAvailability != nil && configuration.Redis.HighAvailability.SentinelName != "" {
			addrs := make([]string, 0)

			if configuration.Redis.Host != "" {
				addrs = append(addrs, fmt.Sprintf("%s:%d", strings.ToLower(configuration.Redis.Host), configuration.Redis.Port))
			}

			for _, node := range configuration.Redis.HighAvailability.Nodes {
				addr := fmt.Sprintf("%s:%d", strings.ToLower(node.Host), node.Port)
				if !utils.IsStringInSlice(addr, addrs) {
					addrs = append(addrs, addr)
				}
			}

			providerName = "redis-sentinel"
			redisSentinelConfig = &redis.FailoverConfig{
				Logger:           &redisLogger{logger: logging.Logger()},
				MasterName:       configuration.Redis.HighAvailability.SentinelName,
				SentinelAddrs:    addrs,
				SentinelUsername: configuration.Redis.HighAvailability.SentinelUsername,
				SentinelPassword: configuration.Redis.HighAvailability.SentinelPassword,
				RouteByLatency:   configuration.Redis.HighAvailability.RouteByLatency,
				RouteRandomly:    configuration.Redis.HighAvailability.RouteRandomly,
				Username:         configuration.Redis.Username,
				Password:         configuration.Redis.Password,
				DB:               configuration.Redis.DatabaseIndex, // DB is the fasthttp/session property for the Redis DB Index.
				PoolSize:         configuration.Redis.MaximumActiveConnections,
				MinIdleConns:     configuration.Redis.MinimumIdleConnections,
				IdleTimeout:      300,
				TLSConfig:        tlsConfig,
				KeyPrefix:        "authelia-session",
			}
		} else {
			providerName = "redis"
			network := "tcp"

			var addr string

			if configuration.Redis.Port == 0 {
				network = "unix"
				addr = configuration.Redis.Host
			} else {
				addr = fmt.Sprintf("%s:%d", configuration.Redis.Host, configuration.Redis.Port)
			}

			redisConfig = &redis.Config{
				Logger:       newRedisLogger(),
				Network:      network,
				Addr:         addr,
				Username:     configuration.Redis.Username,
				Password:     configuration.Redis.Password,
				DB:           configuration.Redis.DatabaseIndex, // DB is the fasthttp/session property for the Redis DB Index.
				PoolSize:     configuration.Redis.MaximumActiveConnections,
				MinIdleConns: configuration.Redis.MinimumIdleConnections,
				IdleTimeout:  300,
				TLSConfig:    tlsConfig,
				KeyPrefix:    "authelia-session",
			}
		}

		config.EncodeFunc = serializer.Encode
		config.DecodeFunc = serializer.Decode
	default:
		providerName = "memory"
	}

	return ProviderConfig{
		config,
		redisConfig,
		redisSentinelConfig,
		providerName,
	}
}