package notmain

import (
	"context"
	"flag"
	"fmt"
	"net"
	"net/netip"
	"os"

	"github.com/jmhodges/clock"

	"github.com/letsencrypt/boulder/cmd"
	bgrpc "github.com/letsencrypt/boulder/grpc"
	"github.com/letsencrypt/boulder/nonce"
	noncepb "github.com/letsencrypt/boulder/nonce/proto"
)

type Config struct {
	NonceService struct {
		cmd.ServiceConfig

		MaxUsed int

		// NonceHMACKey is a path to a file containing an HMAC key which is a
		// secret used for deriving the prefix of each nonce instance. It should
		// contain 256 bits (32 bytes) of random data to be suitable as an
		// HMAC-SHA256 key (e.g. the output of `openssl rand -hex 32`). In a
		// multi-DC deployment this value should be the same across all
		// boulder-wfe and nonce-service instances.
		NonceHMACKey cmd.HMACKeyConfig `validate:"required"`

		Syslog        cmd.SyslogConfig
		OpenTelemetry cmd.OpenTelemetryConfig
	}
}

func derivePrefix(key []byte, grpcAddr string) (string, error) {
	host, port, err := net.SplitHostPort(grpcAddr)
	if err != nil {
		return "", fmt.Errorf("parsing gRPC listen address: %w", err)
	}
	if host == "" {
		return "", fmt.Errorf("nonce service gRPC address must include an IP address: got %q", grpcAddr)
	}
	if host != "" && port != "" {
		hostIP, err := netip.ParseAddr(host)
		if err != nil {
			return "", fmt.Errorf("gRPC address host part was not an IP address")
		}
		if hostIP.IsUnspecified() {
			return "", fmt.Errorf("nonce service gRPC address must be a specific IP address: got %q", grpcAddr)
		}
	}
	return nonce.DerivePrefix(grpcAddr, key), nil
}

func main() {
	grpcAddr := flag.String("addr", "", "gRPC listen address override. Also used to derive the nonce prefix.")
	debugAddr := flag.String("debug-addr", "", "Debug server address override")
	configFile := flag.String("config", "", "File path to the configuration file for this service")
	flag.Parse()

	if *configFile == "" {
		flag.Usage()
		os.Exit(1)
	}

	var c Config
	err := cmd.ReadConfigFile(*configFile, &c)
	cmd.FailOnError(err, "Reading JSON config file into config structure")

	if *grpcAddr != "" {
		c.NonceService.GRPC.Address = *grpcAddr
	}
	if *debugAddr != "" {
		c.NonceService.DebugAddr = *debugAddr
	}

	key, err := c.NonceService.NonceHMACKey.Load()
	cmd.FailOnError(err, "Failed to load nonceHMACKey file.")

	noncePrefix, err := derivePrefix(key, c.NonceService.GRPC.Address)
	cmd.FailOnError(err, "Failed to derive nonce prefix")

	scope, logger, oTelShutdown := cmd.StatsAndLogging(c.NonceService.Syslog, c.NonceService.OpenTelemetry, c.NonceService.DebugAddr)
	defer oTelShutdown(context.Background())
	cmd.LogStartup(logger)

	ns, err := nonce.NewNonceService(scope, c.NonceService.MaxUsed, noncePrefix)
	cmd.FailOnError(err, "Failed to initialize nonce service")

	tlsConfig, err := c.NonceService.TLS.Load(scope)
	cmd.FailOnError(err, "tlsConfig config")

	start, err := bgrpc.NewServer(c.NonceService.GRPC, logger).Add(
		&noncepb.NonceService_ServiceDesc, ns).Build(tlsConfig, scope, clock.New())
	cmd.FailOnError(err, "Unable to setup nonce service gRPC server")

	logger.Infof("Nonce server listening on %s with prefix %q", c.NonceService.GRPC.Address, noncePrefix)

	cmd.FailOnError(start(), "Nonce service gRPC server failed")
}

func init() {
	cmd.RegisterCommand("nonce-service", main, &cmd.ConfigValidator{Config: &Config{}})
}
