// Copyright IBM Corp. 2016, 2025
// SPDX-License-Identifier: MPL-2.0

package rotation

import (
	"fmt"
	"time"

	"github.com/robfig/cron/v3"
)

const (
	PerformedRegistration   = "registration"
	PerformedDeregistration = "deregistration"
)

// RotationOptions is an embeddable struct to capture common rotation
// settings between a Secret and Auth
type RotationOptions struct {
	// Schedule holds the info for the framework.Schedule
	Schedule *RotationSchedule
}

// RotationJob represents the secret part of a response.
type RotationJob struct {
	RotationOptions

	// RotationID is the ID returned to the user to manage this secret.
	// This is generated by Vault core. Any set value will be ignored.
	// For requests, this will always be blank.
	RotationID string `sentinel:""`
	Path       string
	MountPoint string
	Name       string
}

type RotationJobConfigureRequest struct {
	Name             string
	MountPoint       string
	ReqPath          string
	RotationSchedule string
	RotationWindow   time.Duration
	RotationPeriod   time.Duration
}

type RotationJobDeregisterRequest struct {
	MountPoint string
	ReqPath    string
}

// RotationInfoRequest is the request struct used by SystemView.GetRotationInformation.
type RotationInfoRequest struct {
	// ReqPath is the plugin-local path to the credential, and needs to match the ReqPath value that
	// was supplied in schedule creation with RegisterRotationJob
	ReqPath string
}

// RotationInfoResponse is the response struct returned by SystemView.GetRotationInformation.
type RotationInfoResponse struct {
	// NextVaultRotation is the scheduled time of the next rotation.
	NextVaultRotation time.Time

	// LastVaultRotation is the time of the prior rotation.
	LastVaultRotation time.Time

	// TTL is integer seconds until next rotation, conventionally clamped to 0 (i.e., will not be negative)
	TTL int64
}

func (s *RotationJob) Validate() error {
	if s.MountPoint == "" {
		return fmt.Errorf("MountPoint is required")
	}

	if s.Path == "" {
		return fmt.Errorf("ReqPath is required")
	}

	if s.Schedule.RotationSchedule == "" && s.Schedule.RotationPeriod.Seconds() == 0 {
		return fmt.Errorf("RotationSchedule or RotationPeriod is required to set up rotation job")
	}

	return nil
}

func newRotationJob(configRequest *RotationJobConfigureRequest) (*RotationJob, error) {
	var cronSc *cron.SpecSchedule
	if configRequest.RotationSchedule != "" {
		var err error
		cronSc, err = DefaultScheduler.Parse(configRequest.RotationSchedule)
		if err != nil {
			return nil, err
		}
	}

	rs := &RotationSchedule{
		Schedule:          cronSc,
		RotationSchedule:  configRequest.RotationSchedule,
		RotationWindow:    configRequest.RotationWindow,
		RotationPeriod:    configRequest.RotationPeriod,
		NextVaultRotation: time.Time{},
		LastVaultRotation: time.Time{},
	}

	return &RotationJob{
		RotationOptions: RotationOptions{
			Schedule: rs,
		},
		MountPoint: configRequest.MountPoint,
		Path:       configRequest.ReqPath,
		Name:       configRequest.Name,
	}, nil
}

// ConfigureRotationJob builds and returns a configured RotationJob for the mount and request with the given schedule.
func ConfigureRotationJob(configRequest *RotationJobConfigureRequest) (*RotationJob, error) {
	rotationJob, err := newRotationJob(configRequest)
	if err != nil {
		return nil, err
	}

	if err := rotationJob.Validate(); err != nil {
		return nil, fmt.Errorf("error validating rotation job: %s", err)
	}

	// Expect rotation job to exist here
	if rotationJob == nil {
		return nil, fmt.Errorf("rotation credential was nil; expected non-nil value")
	}

	return rotationJob, nil
}
