// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: BUSL-1.1

package issuing

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/hashicorp/go-hclog"
	"github.com/hashicorp/vault/sdk/helper/errutil"
	"github.com/hashicorp/vault/sdk/logical"
)

const StorageIssuerConfig = "config/issuers"

type IssuerConfigEntry struct {
	// This new fetchedDefault field allows us to detect if the default
	// issuer was modified, in turn dispatching the timestamp updater
	// if necessary.
	fetchedDefault             IssuerID `json:"-"`
	DefaultIssuerId            IssuerID `json:"default"`
	DefaultFollowsLatestIssuer bool     `json:"default_follows_latest_issuer"`
}

func GetIssuersConfig(ctx context.Context, s logical.Storage) (*IssuerConfigEntry, error) {
	entry, err := s.Get(ctx, StorageIssuerConfig)
	if err != nil {
		return nil, err
	}

	issuerConfig := &IssuerConfigEntry{}
	if entry != nil {
		if err := entry.DecodeJSON(issuerConfig); err != nil {
			return nil, errutil.InternalError{Err: fmt.Sprintf("unable to decode issuer configuration: %v", err)}
		}
	}
	issuerConfig.fetchedDefault = issuerConfig.DefaultIssuerId

	return issuerConfig, nil
}

func SetIssuersConfig(ctx context.Context, s logical.Storage, config *IssuerConfigEntry) error {
	json, err := logical.StorageEntryJSON(StorageIssuerConfig, config)
	if err != nil {
		return err
	}

	if err := s.Put(ctx, json); err != nil {
		return err
	}

	if err := changeDefaultIssuerTimestamps(ctx, s, config.fetchedDefault, config.DefaultIssuerId); err != nil {
		return err
	}

	return nil
}

func changeDefaultIssuerTimestamps(ctx context.Context, s logical.Storage, oldDefault IssuerID, newDefault IssuerID) error {
	if newDefault == oldDefault {
		return nil
	}

	now := time.Now().UTC()

	// When the default issuer changes, we need to modify four
	// pieces of information:
	//
	// 1. The old default issuer's modification time, as it no
	//    longer works for the /cert/ca path.
	// 2. The new default issuer's modification time, as it now
	//    works for the /cert/ca path.
	// 3. & 4. Both issuer's CRLs, as they behave the same, under
	//    the /cert/crl path!
	for _, thisId := range []IssuerID{oldDefault, newDefault} {
		if len(thisId) == 0 {
			continue
		}

		// 1 & 2 above.
		issuer, err := FetchIssuerById(ctx, s, thisId)
		if err != nil {
			// Due to the lack of transactions, if we deleted the default
			// issuer (successfully), but the subsequent issuer config write
			// (to clear the default issuer's old id) failed, we might have
			// an inconsistent config. If we later hit this loop (and flush
			// these timestamps again -- perhaps because the operator
			// selected a new default), we'd have erred out here, because
			// the since-deleted default issuer doesn't exist. In this case,
			// skip the issuer instead of bailing.
			err := fmt.Errorf("unable to update issuer (%v)'s modification time: error fetching issuer: %w", thisId, err)
			if strings.Contains(err.Error(), "does not exist") {
				hclog.L().Warn(err.Error())
				continue
			}

			return err
		}

		issuer.LastModified = now
		err = WriteIssuer(ctx, s, issuer)
		if err != nil {
			return fmt.Errorf("unable to update issuer (%v)'s modification time: error persisting issuer: %w", thisId, err)
		}
	}

	// Fetch and update the internalCRLConfigEntry (3&4).
	cfg, err := GetLocalCRLConfig(ctx, s)
	if err != nil {
		return fmt.Errorf("unable to update local CRL config's modification time: error fetching local CRL config: %w", err)
	}

	cfg.LastModified = now
	cfg.DeltaLastModified = now
	err = SetLocalCRLConfig(ctx, s, cfg)
	if err != nil {
		return fmt.Errorf("unable to update local CRL config's modification time: error persisting local CRL config: %w", err)
	}

	return nil
}
