// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package healthcheck

import (
	"bytes"
	"crypto/x509"
	"fmt"

	"github.com/hashicorp/go-secure-stdlib/parseutil"
)

type HardwareBackedRoot struct {
	Enabled bool

	UnsupportedVersion bool

	FetchIssues  map[string]*PathFetch
	IssuerKeyMap map[string]string
	KeyIsManaged map[string]string
}

func NewHardwareBackedRootCheck() Check {
	return &HardwareBackedRoot{
		FetchIssues:  make(map[string]*PathFetch),
		IssuerKeyMap: make(map[string]string),
		KeyIsManaged: make(map[string]string),
	}
}

func (h *HardwareBackedRoot) Name() string {
	return "hardware_backed_root"
}

func (h *HardwareBackedRoot) IsEnabled() bool {
	return h.Enabled
}

func (h *HardwareBackedRoot) DefaultConfig() map[string]interface{} {
	return map[string]interface{}{
		"enabled": false,
	}
}

func (h *HardwareBackedRoot) LoadConfig(config map[string]interface{}) error {
	enabled, err := parseutil.ParseBool(config["enabled"])
	if err != nil {
		return fmt.Errorf("error parsing %v.enabled: %w", h.Name(), err)
	}
	h.Enabled = enabled

	return nil
}

func (h *HardwareBackedRoot) FetchResources(e *Executor) error {
	exit, _, issuers, err := pkiFetchIssuersList(e, func() {
		h.UnsupportedVersion = true
	})
	if exit || err != nil {
		return err
	}

	for _, issuer := range issuers {
		skip, ret, entry, err := pkiFetchIssuerEntry(e, issuer, func() {
			h.UnsupportedVersion = true
		})
		if skip || err != nil || entry == nil {
			if err != nil {
				return err
			}
			h.FetchIssues[issuer] = ret
			continue
		}

		// Ensure we only check Root CAs.
		cert := ret.ParsedCache["certificate"].(*x509.Certificate)
		if !bytes.Equal(cert.RawSubject, cert.RawIssuer) {
			continue
		}
		if err := cert.CheckSignatureFrom(cert); err != nil {
			continue
		}

		// Ensure we only check issuers with keys.
		keyId, present := entry["key_id"].(string)
		if !present || len(keyId) == 0 {
			continue
		}

		h.IssuerKeyMap[issuer] = keyId
		skip, ret, keyEntry, err := pkiFetchKeyEntry(e, keyId, func() {
			h.UnsupportedVersion = true
		})
		if skip || err != nil || keyEntry == nil {
			if err != nil {
				return err
			}

			h.FetchIssues[issuer] = ret
			continue
		}

		uuid, present := keyEntry["managed_key_id"].(string)
		if present {
			h.KeyIsManaged[keyId] = uuid
		}
	}

	return nil
}

func (h *HardwareBackedRoot) Evaluate(e *Executor) (results []*Result, err error) {
	if h.UnsupportedVersion {
		ret := Result{
			Status:   ResultInvalidVersion,
			Endpoint: "/{{mount}}/issuers",
			Message:  "This health check requires Vault 1.11+ but an earlier version of Vault Server was contacted, preventing this health check from running.",
		}
		return []*Result{&ret}, nil
	}

	for issuer, fetchPath := range h.FetchIssues {
		if fetchPath != nil && fetchPath.IsSecretPermissionsError() {
			delete(h.IssuerKeyMap, issuer)
			ret := Result{
				Status:   ResultInsufficientPermissions,
				Endpoint: fetchPath.Path,
				Message:  "Without this information, this health check is unable to function.",
			}

			if e.Client.Token() == "" {
				ret.Message = "No token available so unable for the endpoint for this mount. " + ret.Message
			} else {
				ret.Message = "This token lacks permission for the endpoint for this mount. " + ret.Message
			}

			results = append(results, &ret)
		}
	}

	for name, keyId := range h.IssuerKeyMap {
		var ret Result
		ret.Status = ResultInformational
		ret.Endpoint = "/{{mount}}/issuer/" + name
		ret.Message = "Root issuer was created using Vault-backed software keys; for added safety of long-lived, important root CAs, you may wish to consider using a HSM or KSM Managed Key to store key material for this issuer."

		uuid, present := h.KeyIsManaged[keyId]
		if present {
			ret.Status = ResultOK
			ret.Message = fmt.Sprintf("Root issuer was backed by a HSM or KMS Managed Key: %v.", uuid)
		}

		results = append(results, &ret)
	}

	return
}
