package validator

import (
	"errors"
	"fmt"

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

// ValidateStorage validates storage configuration.
func ValidateStorage(config schema.Storage, validator *schema.StructValidator) {
	if config.EncryptionKey == "" {
		validator.Push(errors.New(errStrStorageEncryptionKeyMustBeProvided))
	} else if len(config.EncryptionKey) < 20 {
		validator.Push(errors.New(errStrStorageEncryptionKeyTooShort))
	}

	if config.Local == nil && config.MySQL == nil && config.PostgreSQL == nil {
		validator.Push(errors.New(errStrStorage))

		return
	}

	var configured []string

	if config.Local != nil {
		configured = append(configured, "local")
	}

	if config.MySQL != nil {
		configured = append(configured, "mysql")
	}

	if config.PostgreSQL != nil {
		configured = append(configured, "postgres")
	}

	if len(configured) > 1 {
		validator.Push(fmt.Errorf(errStrStorageMultiple, utils.StringJoinAnd(configured)))

		return
	}

	if config.Local != nil {
		validateLocalStorageConfiguration(config.Local, validator)
	}

	if config.MySQL != nil {
		validateMySQLConfiguration(config.MySQL, validator)
	}

	if config.PostgreSQL != nil {
		validatePostgreSQLConfiguration(config.PostgreSQL, validator)
	}
}

func validateSQLConfiguration(config, defaults *schema.StorageSQL, validator *schema.StructValidator, provider string) {
	if config.Address == nil {
		validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "address"))
	} else {
		var err error
		if err = config.Address.ValidateSQL(); err != nil {
			validator.Push(fmt.Errorf(errFmtStorageAddressValidate, provider, config.Address.String(), err))
		}
	}

	if config.Address != nil && config.Address.IsTCP() && config.Address.Port() == 0 {
		config.Address.SetPort(defaults.Address.Port())
	}

	if config.Username == "" {
		validator.Push(fmt.Errorf(errFmtStorageUserMustBeProvided, provider))
	}

	if config.Database == "" {
		validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, provider, "database"))
	}

	if config.Timeout == 0 {
		config.Timeout = schema.DefaultSQLStorageConfiguration.Timeout
	}
}

func validateMySQLConfiguration(config *schema.StorageMySQL, validator *schema.StructValidator) {
	validateSQLConfiguration(&config.StorageSQL, &schema.DefaultMySQLStorageConfiguration.StorageSQL, validator, "mysql")

	if config.TLS != nil {
		configDefaultTLS := &schema.TLS{
			MinimumVersion: schema.DefaultMySQLStorageConfiguration.TLS.MinimumVersion,
			MaximumVersion: schema.DefaultMySQLStorageConfiguration.TLS.MaximumVersion,
		}

		if config.Address != nil {
			configDefaultTLS.ServerName = config.Address.Hostname()
		}

		if err := ValidateTLSConfig(config.TLS, configDefaultTLS); err != nil {
			validator.Push(fmt.Errorf(errFmtStorageTLSConfigInvalid, "mysql", err))
		}
	}
}

func validatePostgreSQLConfiguration(config *schema.StoragePostgreSQL, validator *schema.StructValidator) {
	validateSQLConfiguration(&config.StorageSQL, &schema.DefaultPostgreSQLStorageConfiguration.StorageSQL, validator, "postgres")

	if config.Schema == "" {
		config.Schema = schema.DefaultPostgreSQLStorageConfiguration.Schema
	}

	switch {
	case config.TLS != nil && config.SSL != nil: //nolint:staticcheck
		validator.Push(errors.New(errFmtStoragePostgreSQLInvalidSSLAndTLSConfig))
	case config.TLS != nil:
		configDefaultTLS := &schema.TLS{
			ServerName:     config.Address.Hostname(),
			MinimumVersion: schema.DefaultPostgreSQLStorageConfiguration.TLS.MinimumVersion,
			MaximumVersion: schema.DefaultPostgreSQLStorageConfiguration.TLS.MaximumVersion,
		}

		if err := ValidateTLSConfig(config.TLS, configDefaultTLS); err != nil {
			validator.Push(fmt.Errorf(errFmtStorageTLSConfigInvalid, "postgres", err))
		}
	case config.SSL != nil: //nolint:staticcheck
		validator.PushWarning(errors.New(warnFmtStoragePostgreSQLInvalidSSLDeprecated))

		switch {
		case config.SSL.Mode == "": //nolint:staticcheck
			config.SSL.Mode = schema.DefaultPostgreSQLStorageConfiguration.SSL.Mode //nolint:staticcheck
		case !utils.IsStringInSlice(config.SSL.Mode, validStoragePostgreSQLSSLModes): //nolint:staticcheck
			validator.Push(fmt.Errorf(errFmtStoragePostgreSQLInvalidSSLMode, utils.StringJoinOr(validStoragePostgreSQLSSLModes), config.SSL.Mode)) //nolint:staticcheck
		}
	}

	validatePostgreSQLConfigurationServers(config, validator)
}

func validatePostgreSQLConfigurationServers(config *schema.StoragePostgreSQL, validator *schema.StructValidator) {
	for i, server := range config.Servers {
		description := fmt.Sprintf("postgres: servers: #%d", i+1)

		if server.Address == nil {
			validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, description, "address"))
		} else {
			var err error
			if err = server.Address.ValidateSQL(); err != nil {
				validator.Push(fmt.Errorf(errFmtStorageAddressValidate, description, server.Address.String(), err))
			}
		}

		if server.Address != nil && server.Address.IsTCP() && server.Address.Port() == 0 {
			server.Address.SetPort(schema.DefaultPostgreSQLStorageConfiguration.Address.Port())
		}

		if server.TLS != nil {
			configDefaultTLS := &schema.TLS{
				ServerName: server.Address.Hostname(),
			}

			if config.TLS != nil {
				configDefaultTLS.MinimumVersion = config.TLS.MinimumVersion
				configDefaultTLS.MaximumVersion = config.TLS.MaximumVersion
			} else {
				configDefaultTLS.MinimumVersion = schema.DefaultPostgreSQLStorageConfiguration.TLS.MinimumVersion
				configDefaultTLS.MaximumVersion = schema.DefaultPostgreSQLStorageConfiguration.TLS.MaximumVersion
			}

			if err := ValidateTLSConfig(server.TLS, configDefaultTLS); err != nil {
				validator.Push(fmt.Errorf(errFmtStorageTLSConfigInvalid, description, err))
			}
		}
	}
}

func validateLocalStorageConfiguration(config *schema.StorageLocal, validator *schema.StructValidator) {
	if config.Path == "" {
		validator.Push(fmt.Errorf(errFmtStorageOptionMustBeProvided, "local", "path"))
	}
}
