package retention

import (
	"context"
	"time"

	"github.com/alcionai/clues"
	"github.com/kopia/kopia/repo/blob"
	"github.com/kopia/kopia/repo/format"
	"github.com/kopia/kopia/repo/maintenance"

	"github.com/alcionai/corso/src/pkg/control/repository"
)

type Opts struct {
	blobCfg format.BlobStorageConfiguration
	params  maintenance.Params

	blobChanged   bool
	paramsChanged bool
}

func NewOpts() *Opts {
	return &Opts{}
}

func OptsFromConfigs(
	blobCfg format.BlobStorageConfiguration,
	params maintenance.Params,
) *Opts {
	return &Opts{
		blobCfg: blobCfg,
		params:  params,
	}
}

func (r *Opts) AsConfigs(
	ctx context.Context,
) (format.BlobStorageConfiguration, maintenance.Params, error) {
	// Check the new config is valid.
	if r.blobCfg.IsRetentionEnabled() {
		if err := maintenance.CheckExtendRetention(ctx, r.blobCfg, &r.params); err != nil {
			return format.BlobStorageConfiguration{}, maintenance.Params{}, clues.WrapWC(
				ctx,
				err,
				"invalid retention config")
		}
	}

	return r.blobCfg, r.params, nil
}

func (r *Opts) BlobChanged() bool {
	return r.blobChanged
}

func (r *Opts) ParamsChanged() bool {
	return r.paramsChanged
}

func (r *Opts) Set(opts repository.Retention) error {
	r.setMaintenanceParams(opts.Extend)

	return clues.Wrap(
		r.setBlobConfigParams(opts.Mode, opts.Duration),
		"setting mode or duration").OrNil()
}

func (r *Opts) setMaintenanceParams(extend *bool) {
	if extend != nil && r.params.ExtendObjectLocks != *extend {
		r.params.ExtendObjectLocks = *extend
		r.paramsChanged = true
	}
}

func (r *Opts) setBlobConfigParams(
	mode *repository.RetentionMode,
	duration *time.Duration,
) error {
	err := r.setBlobConfigMode(mode)
	if err != nil {
		return clues.Stack(err)
	}

	r.setBlobConfigDuration(duration)

	return nil
}

func (r *Opts) setBlobConfigDuration(duration *time.Duration) {
	if duration != nil && r.blobCfg.RetentionPeriod != *duration {
		r.blobCfg.RetentionPeriod = *duration
		r.blobChanged = true
	}
}

func (r *Opts) setBlobConfigMode(
	mode *repository.RetentionMode,
) error {
	if mode == nil {
		return nil
	}

	startMode := r.blobCfg.RetentionMode

	switch *mode {
	case repository.NoRetention:
		if !r.blobCfg.IsRetentionEnabled() {
			return nil
		}

		r.blobCfg.RetentionMode = ""
		r.blobCfg.RetentionPeriod = 0

	case repository.GovernanceRetention:
		r.blobCfg.RetentionMode = blob.Governance

	case repository.ComplianceRetention:
		r.blobCfg.RetentionMode = blob.Compliance

	default:
		return clues.New("unknown retention mode").
			With("provided_retention_mode", mode.String())
	}

	// Only check if the retention mode is not empty. IsValid errors out if it's
	// empty.
	if len(r.blobCfg.RetentionMode) > 0 && !r.blobCfg.RetentionMode.IsValid() {
		return clues.New("invalid retention mode").
			With("retention_mode", r.blobCfg.RetentionMode)
	}

	// Take into account previous operations on r that could have already updated
	// blobChanged.
	r.blobChanged = r.blobChanged || startMode != r.blobCfg.RetentionMode

	return nil
}

// Verify checks that the config info in r passes kopia's retention validation
// checks when it comes to locking durations and that if retention is requested
// in the blob config blob then lock extension is also configured to run during
// maintenance. If rentention is not enabled in the blob config blob then lock
// extension should be disabled during maintenance.
func (r Opts) Verify(ctx context.Context) error {
	if !r.blobCfg.IsRetentionEnabled() {
		if r.params.ExtendObjectLocks {
			return clues.NewWC(
				ctx,
				"retention disabled but maintenance lock extension enabled")
		}

		// Both disabled.
		return nil
	}

	// Rest of function handles case where retention is enabled in the blob config
	// blob.
	if !r.params.ExtendObjectLocks {
		return clues.NewWC(
			ctx,
			"retention enabled but maintenance lock extension disabled")
	}

	return clues.Stack(maintenance.CheckExtendRetention(
		ctx,
		r.blobCfg,
		&r.params)).OrNil()
}
