// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package healthcheck

import (
	"crypto/x509"
	"encoding/pem"
	"fmt"

	"github.com/hashicorp/vault/sdk/logical"
)

func pkiFetchIssuersList(e *Executor, versionError func()) (bool, *PathFetch, []string, error) {
	issuersRet, err := e.FetchIfNotFetched(logical.ListOperation, "/{{mount}}/issuers")
	if err != nil {
		return true, issuersRet, nil, err
	}

	if !issuersRet.IsSecretOK() {
		if issuersRet.IsUnsupportedPathError() {
			versionError()
		}

		if issuersRet.Is404NotFound() {
			return true, issuersRet, nil, fmt.Errorf("this mount lacks any configured issuers, limiting health check usefulness")
		}

		return true, issuersRet, nil, nil
	}

	if len(issuersRet.ParsedCache) == 0 {
		var issuers []string
		for _, rawIssuerId := range issuersRet.Secret.Data["keys"].([]interface{}) {
			issuers = append(issuers, rawIssuerId.(string))
		}
		issuersRet.ParsedCache["issuers"] = issuers
	}

	return false, issuersRet, issuersRet.ParsedCache["issuers"].([]string), nil
}

func parsePEM(contents string) ([]byte, error) {
	// Need to parse out the issuer from its PEM format.
	pemBlock, _ := pem.Decode([]byte(contents))
	if pemBlock == nil {
		return nil, fmt.Errorf("invalid PEM block")
	}

	return pemBlock.Bytes, nil
}

func ParsePEMCert(contents string) (*x509.Certificate, error) {
	parsed, err := parsePEM(contents)
	if err != nil {
		return nil, err
	}

	cert, err := x509.ParseCertificate(parsed)
	if err != nil {
		return nil, fmt.Errorf("invalid certificate: %w", err)
	}

	return cert, nil
}

func parsePEMCRL(contents string) (*x509.RevocationList, error) {
	parsed, err := parsePEM(contents)
	if err != nil {
		return nil, err
	}

	crl, err := x509.ParseRevocationList(parsed)
	if err != nil {
		return nil, fmt.Errorf("invalid CRL: %w", err)
	}

	return crl, nil
}

func pkiFetchIssuer(e *Executor, issuer string, versionError func()) (bool, *PathFetch, *x509.Certificate, error) {
	issuerRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/issuer/"+issuer+"/json")
	if err != nil {
		return true, issuerRet, nil, err
	}

	if !issuerRet.IsSecretOK() {
		if issuerRet.IsUnsupportedPathError() {
			versionError()
		}
		return true, issuerRet, nil, nil
	}

	if len(issuerRet.ParsedCache) == 0 {
		cert, err := ParsePEMCert(issuerRet.Secret.Data["certificate"].(string))
		if err != nil {
			return true, issuerRet, nil, fmt.Errorf("unable to parse issuer %v's certificate: %w", issuer, err)
		}

		issuerRet.ParsedCache["certificate"] = cert
	}

	return false, issuerRet, issuerRet.ParsedCache["certificate"].(*x509.Certificate), nil
}

func pkiFetchIssuerEntry(e *Executor, issuer string, versionError func()) (bool, *PathFetch, map[string]interface{}, error) {
	issuerRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/issuer/"+issuer)
	if err != nil {
		return true, issuerRet, nil, err
	}

	if !issuerRet.IsSecretOK() {
		if issuerRet.IsUnsupportedPathError() {
			versionError()
		}
		return true, issuerRet, nil, nil
	}

	if len(issuerRet.ParsedCache) == 0 {
		cert, err := ParsePEMCert(issuerRet.Secret.Data["certificate"].(string))
		if err != nil {
			return true, issuerRet, nil, fmt.Errorf("unable to parse issuer %v's certificate: %w", issuer, err)
		}

		issuerRet.ParsedCache["certificate"] = cert
	}

	var data map[string]interface{} = nil
	if issuerRet.Secret != nil && len(issuerRet.Secret.Data) > 0 {
		data = issuerRet.Secret.Data
	}

	return false, issuerRet, data, nil
}

func pkiFetchIssuerCRL(e *Executor, issuer string, delta bool, versionError func()) (bool, *PathFetch, *x509.RevocationList, error) {
	path := "/{{mount}}/issuer/" + issuer + "/crl"
	name := "CRL"
	if delta {
		path += "/delta"
		name = "Delta CRL"
	}

	crlRet, err := e.FetchIfNotFetched(logical.ReadOperation, path)
	if err != nil {
		return true, crlRet, nil, err
	}

	if !crlRet.IsSecretOK() {
		if crlRet.IsUnsupportedPathError() {
			versionError()
		}
		return true, crlRet, nil, nil
	}

	if len(crlRet.ParsedCache) == 0 {
		crl, err := parsePEMCRL(crlRet.Secret.Data["crl"].(string))
		if err != nil {
			return true, crlRet, nil, fmt.Errorf("unable to parse issuer %v's %v: %w", issuer, name, err)
		}
		crlRet.ParsedCache["crl"] = crl
	}

	return false, crlRet, crlRet.ParsedCache["crl"].(*x509.RevocationList), nil
}

func pkiFetchKeyEntry(e *Executor, key string, versionError func()) (bool, *PathFetch, map[string]interface{}, error) {
	keyRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/key/"+key)
	if err != nil {
		return true, keyRet, nil, err
	}

	if !keyRet.IsSecretOK() {
		if keyRet.IsUnsupportedPathError() {
			versionError()
		}
		return true, keyRet, nil, nil
	}

	var data map[string]interface{} = nil
	if keyRet.Secret != nil && len(keyRet.Secret.Data) > 0 {
		data = keyRet.Secret.Data
	}

	return false, keyRet, data, nil
}

func pkiFetchLeavesList(e *Executor, versionError func()) (bool, *PathFetch, []string, error) {
	leavesRet, err := e.FetchIfNotFetched(logical.ListOperation, "/{{mount}}/certs")
	if err != nil {
		return true, leavesRet, nil, err
	}

	if !leavesRet.IsSecretOK() {
		if leavesRet.IsUnsupportedPathError() {
			versionError()
		}

		return true, leavesRet, nil, nil
	}

	if len(leavesRet.ParsedCache) == 0 {
		var leaves []string
		for _, rawSerial := range leavesRet.Secret.Data["keys"].([]interface{}) {
			leaves = append(leaves, rawSerial.(string))
		}
		leavesRet.ParsedCache["leaves"] = leaves
		leavesRet.ParsedCache["count"] = len(leaves)
	}

	return false, leavesRet, leavesRet.ParsedCache["leaves"].([]string), nil
}

func pkiFetchLeaf(e *Executor, serial string, versionError func()) (bool, *PathFetch, *x509.Certificate, error) {
	leafRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/cert/"+serial)
	if err != nil {
		return true, leafRet, nil, err
	}

	if !leafRet.IsSecretOK() {
		if leafRet.IsUnsupportedPathError() {
			versionError()
		}
		return true, leafRet, nil, nil
	}

	if len(leafRet.ParsedCache) == 0 {
		cert, err := ParsePEMCert(leafRet.Secret.Data["certificate"].(string))
		if err != nil {
			return true, leafRet, nil, fmt.Errorf("unable to parse leaf %v's certificate: %w", serial, err)
		}

		leafRet.ParsedCache["certificate"] = cert
	}

	return false, leafRet, leafRet.ParsedCache["certificate"].(*x509.Certificate), nil
}

func pkiFetchRolesList(e *Executor, versionError func()) (bool, *PathFetch, []string, error) {
	rolesRet, err := e.FetchIfNotFetched(logical.ListOperation, "/{{mount}}/roles")
	if err != nil {
		return true, rolesRet, nil, err
	}

	if !rolesRet.IsSecretOK() {
		if rolesRet.IsUnsupportedPathError() {
			versionError()
		}

		return true, rolesRet, nil, nil
	}

	if len(rolesRet.ParsedCache) == 0 {
		var roles []string
		for _, roleName := range rolesRet.Secret.Data["keys"].([]interface{}) {
			roles = append(roles, roleName.(string))
		}
		rolesRet.ParsedCache["roles"] = roles
	}

	return false, rolesRet, rolesRet.ParsedCache["roles"].([]string), nil
}

func pkiFetchRole(e *Executor, name string, versionError func()) (bool, *PathFetch, map[string]interface{}, error) {
	roleRet, err := e.FetchIfNotFetched(logical.ReadOperation, "/{{mount}}/roles/"+name)
	if err != nil {
		return true, roleRet, nil, err
	}

	if !roleRet.IsSecretOK() {
		if roleRet.IsUnsupportedPathError() {
			versionError()
		}
		return true, roleRet, nil, nil
	}

	var data map[string]interface{} = nil
	if roleRet.Secret != nil && len(roleRet.Secret.Data) > 0 {
		data = roleRet.Secret.Data
	}

	return false, roleRet, data, nil
}
