// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package secrets

import (
	"context"
	"errors"
	"fmt"
	"net/netip"
	"net/url"

	"github.com/cosi-project/runtime/pkg/controller"
	"github.com/cosi-project/runtime/pkg/controller/generic"
	"github.com/cosi-project/runtime/pkg/controller/generic/transform"
	"github.com/siderolabs/crypto/x509"
	"github.com/siderolabs/gen/optional"
	"go.uber.org/zap"

	"github.com/siderolabs/talos/pkg/machinery/constants"
	"github.com/siderolabs/talos/pkg/machinery/resources/config"
	"github.com/siderolabs/talos/pkg/machinery/resources/secrets"
)

func rootMapFunc[Output generic.ResourceWithRD](output Output, requireControlPlane bool) func(cfg *config.MachineConfig) optional.Optional[Output] {
	return func(cfg *config.MachineConfig) optional.Optional[Output] {
		if cfg.Metadata().ID() != config.ActiveID {
			return optional.None[Output]()
		}

		if cfg.Config().Cluster() == nil || cfg.Config().Machine() == nil {
			return optional.None[Output]()
		}

		if requireControlPlane && !cfg.Config().Machine().Type().IsControlPlane() {
			return optional.None[Output]()
		}

		return optional.Some(output)
	}
}

// RootEtcdController manages secrets.EtcdRoot based on configuration.
type RootEtcdController = transform.Controller[*config.MachineConfig, *secrets.EtcdRoot]

// NewRootEtcdController instanciates the controller.
func NewRootEtcdController() *RootEtcdController {
	return transform.NewController(
		transform.Settings[*config.MachineConfig, *secrets.EtcdRoot]{
			Name:                    "secrets.RootEtcdController",
			MapMetadataOptionalFunc: rootMapFunc(secrets.NewEtcdRoot(secrets.EtcdRootID), true),
			TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, cfg *config.MachineConfig, res *secrets.EtcdRoot) error {
				cfgProvider := cfg.Config()
				etcdSecrets := res.TypedSpec()

				etcdSecrets.EtcdCA = cfgProvider.Cluster().Etcd().CA()

				if etcdSecrets.EtcdCA == nil {
					return errors.New("missing cluster.etcdCA secret")
				}

				return nil
			},
		},
	)
}

// RootKubernetesController manages secrets.KubernetesRoot based on configuration.
type RootKubernetesController = transform.Controller[*config.MachineConfig, *secrets.KubernetesRoot]

// NewRootKubernetesController instanciates the controller.
func NewRootKubernetesController() *RootKubernetesController {
	return transform.NewController(
		transform.Settings[*config.MachineConfig, *secrets.KubernetesRoot]{
			Name:                    "secrets.RootKubernetesController",
			MapMetadataOptionalFunc: rootMapFunc(secrets.NewKubernetesRoot(secrets.KubernetesRootID), true),
			TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, cfg *config.MachineConfig, res *secrets.KubernetesRoot) error {
				cfgProvider := cfg.Config()
				k8sSecrets := res.TypedSpec()

				var (
					err           error
					localEndpoint *url.URL
				)

				if cfgProvider.Machine().Features().KubePrism().Enabled() {
					localEndpoint, err = url.Parse(fmt.Sprintf("https://127.0.0.1:%d", cfgProvider.Machine().Features().KubePrism().Port()))
					if err != nil {
						return err
					}
				} else {
					localEndpoint, err = url.Parse(fmt.Sprintf("https://localhost:%d", cfgProvider.Cluster().LocalAPIServerPort()))
					if err != nil {
						return err
					}
				}

				k8sSecrets.Name = cfgProvider.Cluster().Name()
				k8sSecrets.Endpoint = cfgProvider.Cluster().Endpoint()
				k8sSecrets.LocalEndpoint = localEndpoint
				k8sSecrets.CertSANs = cfgProvider.Cluster().CertSANs()
				k8sSecrets.DNSDomain = cfgProvider.Cluster().Network().DNSDomain()

				k8sSecrets.APIServerIPs, err = cfgProvider.Cluster().Network().APIServerIPs()
				if err != nil {
					return fmt.Errorf("error building API service IPs: %w", err)
				}

				k8sSecrets.AggregatorCA = cfgProvider.Cluster().AggregatorCA()

				if k8sSecrets.AggregatorCA == nil {
					return errors.New("missing cluster.aggregatorCA secret")
				}

				k8sSecrets.IssuingCA = cfgProvider.Cluster().IssuingCA()
				k8sSecrets.AcceptedCAs = cfgProvider.Cluster().AcceptedCAs()

				if k8sSecrets.IssuingCA != nil {
					k8sSecrets.AcceptedCAs = append(k8sSecrets.AcceptedCAs, &x509.PEMEncodedCertificate{
						Crt: k8sSecrets.IssuingCA.Crt,
					})
				}

				if len(k8sSecrets.IssuingCA.Key) == 0 {
					// drop incomplete issuing CA, as the machine config for workers contains just the cert
					k8sSecrets.IssuingCA = nil
				}

				if len(k8sSecrets.AcceptedCAs) == 0 {
					return errors.New("missing cluster.CA secret")
				}

				k8sSecrets.ServiceAccount = cfgProvider.Cluster().ServiceAccount()

				k8sSecrets.AESCBCEncryptionSecret = cfgProvider.Cluster().AESCBCEncryptionSecret()
				k8sSecrets.SecretboxEncryptionSecret = cfgProvider.Cluster().SecretboxEncryptionSecret()

				k8sSecrets.BootstrapTokenID = cfgProvider.Cluster().Token().ID()
				k8sSecrets.BootstrapTokenSecret = cfgProvider.Cluster().Token().Secret()

				return nil
			},
		},
	)
}

// RootOSController manages secrets.OSRoot based on configuration.
type RootOSController = transform.Controller[*config.MachineConfig, *secrets.OSRoot]

// NewRootOSController instanciates the controller.
func NewRootOSController() *RootOSController {
	return transform.NewController(
		transform.Settings[*config.MachineConfig, *secrets.OSRoot]{
			Name:                    "secrets.RootOSController",
			MapMetadataOptionalFunc: rootMapFunc(secrets.NewOSRoot(secrets.OSRootID), false),
			TransformFunc: func(ctx context.Context, r controller.Reader, logger *zap.Logger, cfg *config.MachineConfig, res *secrets.OSRoot) error {
				cfgProvider := cfg.Config()
				osSecrets := res.TypedSpec()

				osSecrets.IssuingCA = cfgProvider.Machine().Security().IssuingCA()
				osSecrets.AcceptedCAs = cfgProvider.Machine().Security().AcceptedCAs()

				if osSecrets.IssuingCA != nil {
					osSecrets.AcceptedCAs = append(osSecrets.AcceptedCAs, &x509.PEMEncodedCertificate{
						Crt: osSecrets.IssuingCA.Crt,
					})

					if len(osSecrets.IssuingCA.Key) == 0 {
						// drop incomplete issuing CA, as the machine config for workers contains just the cert
						osSecrets.IssuingCA = nil
					}
				}

				osSecrets.CertSANIPs = nil
				osSecrets.CertSANDNSNames = nil

				for _, san := range cfgProvider.Machine().Security().CertSANs() {
					if ip, err := netip.ParseAddr(san); err == nil {
						osSecrets.CertSANIPs = append(osSecrets.CertSANIPs, ip)
					} else {
						osSecrets.CertSANDNSNames = append(osSecrets.CertSANDNSNames, san)
					}
				}

				if cfgProvider.Machine().Features().KubernetesTalosAPIAccess().Enabled() {
					// add Kubernetes Talos service name to the list of SANs
					osSecrets.CertSANDNSNames = append(osSecrets.CertSANDNSNames,
						constants.KubernetesTalosAPIServiceName,
						constants.KubernetesTalosAPIServiceName+"."+constants.KubernetesTalosAPIServiceNamespace,
					)
				}

				osSecrets.Token = cfgProvider.Machine().Security().Token()

				return nil
			},
		},
	)
}
