// 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 config

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"
)

// VersionContract describes Talos version to generate config for.
//
// Config generation only supports backwards compatibility (e.g. Talos 0.9 can generate configs for Talos 0.9 and 0.8).
// Matching version of the machinery package is required to generate configs for the current version of Talos.
//
// Nil value of *VersionContract always describes current version of Talos.
type VersionContract struct {
	Major int
	Minor int
}

// Well-known Talos version contracts.
var (
	TalosVersionCurrent = (*VersionContract)(nil)
	TalosVersion1_14    = &VersionContract{1, 14}
	TalosVersion1_13    = &VersionContract{1, 13}
	TalosVersion1_12    = &VersionContract{1, 12}
	TalosVersion1_11    = &VersionContract{1, 11}
	TalosVersion1_10    = &VersionContract{1, 10}
	TalosVersion1_9     = &VersionContract{1, 9}
	TalosVersion1_8     = &VersionContract{1, 8}
	TalosVersion1_7     = &VersionContract{1, 7}
	TalosVersion1_6     = &VersionContract{1, 6}
	TalosVersion1_5     = &VersionContract{1, 5}
	TalosVersion1_4     = &VersionContract{1, 4}
	TalosVersion1_3     = &VersionContract{1, 3}
	TalosVersion1_2     = &VersionContract{1, 2}
	TalosVersion1_1     = &VersionContract{1, 1}
	TalosVersion1_0     = &VersionContract{1, 0}
)

var versionRegexp = regexp.MustCompile(`^v(\d+)\.(\d+)($|\.)`)

// ParseContractFromVersion parses Talos version into VersionContract.
func ParseContractFromVersion(version string) (*VersionContract, error) {
	version = "v" + strings.TrimPrefix(version, "v")

	matches := versionRegexp.FindStringSubmatch(version)
	if len(matches) < 3 {
		return nil, fmt.Errorf("error parsing version %q", version)
	}

	var contract VersionContract

	contract.Major, _ = strconv.Atoi(matches[1]) //nolint:errcheck
	contract.Minor, _ = strconv.Atoi(matches[2]) //nolint:errcheck

	return &contract, nil
}

// String returns string representation of the contract.
func (contract *VersionContract) String() string {
	if contract == nil {
		return "current"
	}

	return fmt.Sprintf("v%d.%d", contract.Major, contract.Minor)
}

// Greater compares contract to another contract.
func (contract *VersionContract) Greater(other *VersionContract) bool {
	if contract == nil {
		return other != nil
	}

	if other == nil {
		return false
	}

	return contract.Major > other.Major || (contract.Major == other.Major && contract.Minor > other.Minor)
}

// PodSecurityAdmissionEnabled returns true if pod security admission should be enabled by default.
func (contract *VersionContract) PodSecurityAdmissionEnabled() bool {
	return contract.Greater(TalosVersion1_0)
}

// StableHostnameEnabled returns true if stable hostname generation should be enabled by default.
func (contract *VersionContract) StableHostnameEnabled() bool {
	return contract.Greater(TalosVersion1_1)
}

// KubeletDefaultRuntimeSeccompProfileEnabled returns true if kubelet seccomp profile should be enabled by default.
func (contract *VersionContract) KubeletDefaultRuntimeSeccompProfileEnabled() bool {
	return contract.Greater(TalosVersion1_1)
}

// KubernetesAlternateImageRegistries returns true if alternate image registries should be enabled by default.
// https://github.com/kubernetes/kubernetes/pull/109938
func (contract *VersionContract) KubernetesAlternateImageRegistries() bool {
	return contract.Greater(TalosVersion1_1) && !contract.Greater(TalosVersion1_2)
}

// KubernetesAllowSchedulingOnControlPlanes returns true if scheduling on control planes should be enabled by default.
func (contract *VersionContract) KubernetesAllowSchedulingOnControlPlanes() bool {
	return contract.Greater(TalosVersion1_1)
}

// KubernetesDiscoveryBackendDisabled returns true if Kubernetes cluster discovery backend should be disabled by default.
func (contract *VersionContract) KubernetesDiscoveryBackendDisabled() bool {
	return contract.Greater(TalosVersion1_1)
}

// ApidExtKeyUsageCheckEnabled returns true if apid should check ext key usage of client certificates.
func (contract *VersionContract) ApidExtKeyUsageCheckEnabled() bool {
	return contract.Greater(TalosVersion1_2)
}

// APIServerAuditPolicySupported returns true if kube-apiserver custom audit policy is supported.
func (contract *VersionContract) APIServerAuditPolicySupported() bool {
	return contract.Greater(TalosVersion1_2)
}

// KubeletManifestsDirectoryDisabled returns true if the manifests directory flag is supported.
func (contract *VersionContract) KubeletManifestsDirectoryDisabled() bool {
	return contract.Greater(TalosVersion1_2)
}

// SecretboxEncryptionSupported returns true if encryption with secretbox is supported.
func (contract *VersionContract) SecretboxEncryptionSupported() bool {
	return contract.Greater(TalosVersion1_2)
}

// DiskQuotaSupportEnabled returns true if XFS filesystems should enable project quota.
func (contract *VersionContract) DiskQuotaSupportEnabled() bool {
	return contract.Greater(TalosVersion1_4)
}

// KubePrismEnabled returns true if KubePrism should be enabled by default.
func (contract *VersionContract) KubePrismEnabled() bool {
	return contract.Greater(TalosVersion1_5)
}

// HostDNSEnabled returns true if host dns router should be enabled by default.
func (contract *VersionContract) HostDNSEnabled() bool {
	return contract.Greater(TalosVersion1_6)
}

// UseRSAServiceAccountKey returns true if version of Talos should use RSA Service Account key for the kube-apiserver.
func (contract *VersionContract) UseRSAServiceAccountKey() bool {
	return contract.Greater(TalosVersion1_6)
}

// ClusterNameForWorkers returns true if version of Talos should put cluster name to the worker machine config.
func (contract *VersionContract) ClusterNameForWorkers() bool {
	return contract.Greater(TalosVersion1_7)
}

// HostDNSForwardKubeDNSToHost returns true if version of Talos forces host dns router to be used as upstream for Kubernetes CoreDNS pods.
func (contract *VersionContract) HostDNSForwardKubeDNSToHost() bool {
	return contract.Greater(TalosVersion1_7)
}

// AddExcludeFromExternalLoadBalancer returns true if the label 'node.kubernetes.io/exclude-from-external-load-balancers' is automatically added
// for controlplane nodes.
func (contract *VersionContract) AddExcludeFromExternalLoadBalancer() bool {
	return contract.Greater(TalosVersion1_7)
}

// SecureBootEnrollEnforcementSupported returns true if version of Talos supports SecureBoot enforcement on enroll.
func (contract *VersionContract) SecureBootEnrollEnforcementSupported() bool {
	return contract.Greater(TalosVersion1_7)
}

// VolumeConfigEncryptionSupported returns true if version of Talos supports disk encryption via VolumeConfig.
func (contract *VersionContract) VolumeConfigEncryptionSupported() bool {
	return contract.Greater(TalosVersion1_10)
}

// MultidocNetworkConfigSupported returns true if version of Talos supports multiple NetworkConfig documents.
func (contract *VersionContract) MultidocNetworkConfigSupported() bool {
	return contract.Greater(TalosVersion1_11)
}

// HideDisablePSP returns true if version of Talos should hide DisablePodSecurityPolicy field from the user.
func (contract *VersionContract) HideDisablePSP() bool {
	return contract.Greater(TalosVersion1_11)
}

// HideRBACAndKeyUsage returns true if version of Talos should hide RBAC and ApidCheckExtKeyUsage fields from the user.
func (contract *VersionContract) HideRBACAndKeyUsage() bool {
	return contract.Greater(TalosVersion1_11)
}

// PopulateClusterSANsFromEndpoint returns true if version of Talos should populate cluster SANs from ControlPlaneEndpoint.
func (contract *VersionContract) PopulateClusterSANsFromEndpoint() bool {
	return !contract.Greater(TalosVersion1_11)
}

// GrubUseUKICmdlineDefault returns true if version of Talos should use UKI cmdline by default.
func (contract *VersionContract) GrubUseUKICmdlineDefault() bool {
	return contract.Greater(TalosVersion1_11)
}

// KubeSpanMultidocConfig returns true if version of Talos should use KubeSpan multi-doc config.
func (contract *VersionContract) KubeSpanMultidocConfig() bool {
	return contract.Greater(TalosVersion1_12)
}

// HostDNSMultidocConfig returns true if version of Talos should use HostDNS multi-doc config.
func (contract *VersionContract) HostDNSMultidocConfig() bool {
	return contract.Greater(TalosVersion1_13)
}

// MultidocKubernetesConfigSupported returns true if version of Talos should use multi-doc Kubernetes config.
func (contract *VersionContract) MultidocKubernetesConfigSupported() bool {
	return contract.Greater(TalosVersion1_13)
}
