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

package framework

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

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

// LeaseExtend is left for backwards compatibility for plugins. This function
// now just passes back the data that was passed into it to be processed in core.
// DEPRECATED
func LeaseExtend(backendIncrement, backendMax time.Duration, systemView logical.SystemView) OperationFunc {
	return func(ctx context.Context, req *logical.Request, data *FieldData) (*logical.Response, error) {
		switch {
		case req.Auth != nil:
			req.Auth.TTL = backendIncrement
			req.Auth.MaxTTL = backendMax
			return &logical.Response{Auth: req.Auth}, nil
		case req.Secret != nil:
			req.Secret.TTL = backendIncrement
			req.Secret.MaxTTL = backendMax
			return &logical.Response{Secret: req.Secret}, nil
		}
		return nil, fmt.Errorf("no lease options for request")
	}
}

// CalculateTTL takes all the user-specified, backend, and system inputs and calculates
// a TTL for a lease
func CalculateTTL(sysView logical.SystemView, increment, backendTTL, period, backendMaxTTL, explicitMaxTTL time.Duration, startTime time.Time) (ttl time.Duration, warnings []string, errors error) {
	// Truncate all times to the second since that is the lowest precision for
	// TTLs
	now := time.Now().Truncate(time.Second)
	if startTime.IsZero() {
		startTime = now
	} else {
		startTime = startTime.Truncate(time.Second)
	}

	// Use the mount's configured max unless the backend specifies
	// something more restrictive (perhaps from a role configuration
	// parameter)
	maxTTL := sysView.MaxLeaseTTL()
	if backendMaxTTL > 0 && backendMaxTTL < maxTTL {
		maxTTL = backendMaxTTL
	}
	if explicitMaxTTL > 0 && explicitMaxTTL < maxTTL {
		maxTTL = explicitMaxTTL
	}

	// Should never happen, but guard anyways
	if maxTTL <= 0 {
		return 0, nil, fmt.Errorf("max TTL must be greater than zero")
	}

	var maxValidTime time.Time
	switch {
	case period > 0:
		// Cap the period value to the sys max_ttl value
		if period > maxTTL {
			warnings = append(warnings,
				fmt.Sprintf("period of %q exceeded the effective max_ttl of %q; period value is capped accordingly",
					humanDuration(period), humanDuration(maxTTL)))
			period = maxTTL
		}
		ttl = period

		if explicitMaxTTL > 0 {
			maxValidTime = startTime.Add(explicitMaxTTL)
		}
	default:
		switch {
		case increment > 0:
			ttl = increment
		case backendTTL > 0:
			ttl = backendTTL
		default:
			ttl = sysView.DefaultLeaseTTL()
		}

		// We cannot go past this time
		maxValidTime = startTime.Add(maxTTL)
	}

	if !maxValidTime.IsZero() {
		// Determine the max valid TTL
		maxValidTTL := maxValidTime.Sub(now)

		// If we are past the max TTL, we shouldn't be in this function...but
		// fast path out if we are
		if maxValidTTL <= 0 {
			return 0, nil, fmt.Errorf("past the max TTL, cannot renew")
		}

		// If the proposed expiration is after the maximum TTL of the lease,
		// cap the increment to whatever is left
		if maxValidTTL-ttl < 0 {
			warnings = append(warnings,
				fmt.Sprintf("TTL of %q exceeded the effective max_ttl of %q; TTL value is capped accordingly",
					humanDuration(ttl), humanDuration(maxValidTTL)))
			ttl = maxValidTTL
		}
	}

	return ttl, warnings, nil
}

// humanDuration prints the time duration without zero elements.
func humanDuration(d time.Duration) string {
	if d == 0 {
		return "0s"
	}

	s := d.String()
	if strings.HasSuffix(s, "m0s") {
		s = s[:len(s)-2]
	}
	if idx := strings.Index(s, "h0m"); idx > 0 {
		s = s[:idx+1] + s[idx+3:]
	}
	return s
}
