package notmain

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

	"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_without_all=NoncePrefixKey,structonly"`

		// NoncePrefixKey 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.
		//
		// TODO(#7632): Remove this and change `NonceHMACKey`'s validation to
		// just `required.`
		//
		// Deprecated: Use NonceHMACKey instead.
		NoncePrefixKey cmd.PasswordConfig `validate:"required_without_all=NonceHMACKey,structonly"`

		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 := net.ParseIP(host)
		if hostIP == 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
	}

	var key []byte
	if c.NonceService.NonceHMACKey.KeyFile != "" {
		key, err = c.NonceService.NonceHMACKey.Load()
		cmd.FailOnError(err, "Failed to load 'nonceHMACKey' file.")
	} else if c.NonceService.NoncePrefixKey.PasswordFile != "" {
		keyString, err := c.NonceService.NoncePrefixKey.Pass()
		cmd.FailOnError(err, "Failed to load 'noncePrefixKey' file.")
		key = []byte(keyString)
	} else {
		cmd.Fail("NonceHMACKey KeyFile or NoncePrefixKey PasswordFile must be set")
	}

	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())
	logger.Info(cmd.VersionString())

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

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

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

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