package server

import (
	"crypto/tls"
	"crypto/x509"
	"net"
	"os"
	"strconv"

	"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/middlewares"
)

// CreateServer Create Authelia's internal webserver with the given configuration and providers.
func CreateServer(config schema.Configuration, providers middlewares.Providers) (*fasthttp.Server, net.Listener) {
	server := &fasthttp.Server{
		ErrorHandler:          handlerError(),
		Handler:               getHandler(config, providers),
		NoDefaultServerHeader: true,
		ReadBufferSize:        config.Server.ReadBufferSize,
		WriteBufferSize:       config.Server.WriteBufferSize,
	}

	logger := logging.Logger()

	address := net.JoinHostPort(config.Server.Host, strconv.Itoa(config.Server.Port))

	var (
		listener         net.Listener
		err              error
		connectionType   string
		connectionScheme string
	)

	if config.Server.TLS.Certificate != "" && config.Server.TLS.Key != "" {
		connectionType, connectionScheme = "TLS", schemeHTTPS

		if err = server.AppendCert(config.Server.TLS.Certificate, config.Server.TLS.Key); err != nil {
			logger.Fatalf("unable to load certificate: %v", err)
		}

		if len(config.Server.TLS.ClientCertificates) > 0 {
			caCertPool := x509.NewCertPool()

			for _, path := range config.Server.TLS.ClientCertificates {
				cert, err := os.ReadFile(path)
				if err != nil {
					logger.Fatalf("Cannot read client TLS certificate %s: %s", path, err)
				}

				caCertPool.AppendCertsFromPEM(cert)
			}

			// ClientCAs should never be nil, otherwise the system cert pool is used for client authentication
			// but we don't want everybody on the Internet to be able to authenticate.
			server.TLSConfig.ClientCAs = caCertPool
			server.TLSConfig.ClientAuth = tls.RequireAndVerifyClientCert
		}

		if listener, err = tls.Listen("tcp", address, server.TLSConfig.Clone()); err != nil {
			logger.Fatalf("Error initializing listener: %s", err)
		}
	} else {
		connectionType, connectionScheme = "non-TLS", schemeHTTP

		if listener, err = net.Listen("tcp", address); err != nil {
			logger.Fatalf("Error initializing listener: %s", err)
		}
	}

	if err = writeHealthCheckEnv(config.Server.DisableHealthcheck, connectionScheme, config.Server.Host,
		config.Server.Path, config.Server.Port); err != nil {
		logger.Fatalf("Could not configure healthcheck: %v", err)
	}

	if config.Server.Path == "" {
		logger.Infof("Initializing server for %s connections on '%s' path '/'", connectionType, listener.Addr().String())
	} else {
		logger.Infof("Initializing server for %s connections on '%s' paths '/' and '%s'", connectionType, listener.Addr().String(), config.Server.Path)
	}

	return server, listener
}