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

package logical

import (
	"context"
	"errors"
)

type snapshotStorageView struct {
	storage    Storage
	snapshotID string
}

var readOnlyErr = errors.New("read-only storage view")

func (s *snapshotStorageView) List(ctx context.Context, prefix string) ([]string, error) {
	return s.storage.List(CreateContextWithSnapshotID(ctx, s.snapshotID), prefix)
}

func (s *snapshotStorageView) Get(ctx context.Context, key string) (*StorageEntry, error) {
	return s.storage.Get(CreateContextWithSnapshotID(ctx, s.snapshotID), key)
}

func (s *snapshotStorageView) Put(_ context.Context, _ *StorageEntry) error {
	return readOnlyErr
}

func (s *snapshotStorageView) Delete(_ context.Context, _ string) error {
	return readOnlyErr
}

// NewSnapshotStorageView creates a storage view that provides read-only access
// to the given snapshot's storage.
func NewSnapshotStorageView(request *Request) (Storage, error) {
	if request.RequiresSnapshotID == "" {
		return nil, errors.New("no snapshot in request")
	}
	return &snapshotStorageView{
		storage:    request.Storage,
		snapshotID: request.RequiresSnapshotID,
	}, nil
}

// SnapshotStorageProvider is an interface that provides a method to retrieve
// the snapshot by ID
type SnapshotStorageProvider interface {
	SnapshotStorage(ctx context.Context, id string) (Storage, error)
}

type SnapshotStorageRouter struct {
	underlying Storage
	manager    SnapshotStorageProvider
}

// NewSnapshotStorageRouter creates a new storage instance that routes to either
// a snapshot (given the snapshot's ID in the context) or to the underlying
// storage
func NewSnapshotStorageRouter(underlying Storage, manager SnapshotStorageProvider) Storage {
	return &SnapshotStorageRouter{
		underlying: underlying,
		manager:    manager,
	}
}

var _ Storage = (*SnapshotStorageRouter)(nil)

func (s *SnapshotStorageRouter) getStorageRead(ctx context.Context) (Storage, error) {
	snapID, ok := ContextSnapshotIDValue(ctx)
	if ok && snapID != "" {
		snapshotStorage, err := s.manager.SnapshotStorage(ctx, snapID)
		if err != nil {
			return nil, err
		}
		return snapshotStorage, nil
	}
	return s.underlying, nil
}

func (s *SnapshotStorageRouter) List(ctx context.Context, key string) ([]string, error) {
	storage, err := s.getStorageRead(ctx)
	if err != nil {
		return nil, err
	}
	return storage.List(ctx, key)
}

func (s *SnapshotStorageRouter) Get(ctx context.Context, key string) (*StorageEntry, error) {
	storage, err := s.getStorageRead(ctx)
	if err != nil {
		return nil, err
	}
	return storage.Get(ctx, key)
}

var WriteErr = errors.New("attempted write operation on snapshot")

func (s *SnapshotStorageRouter) checkWrite(ctx context.Context) error {
	snapID, ok := ContextSnapshotIDValue(ctx)
	if ok && snapID != "" {
		return WriteErr
	}
	return nil
}

func (s *SnapshotStorageRouter) Put(ctx context.Context, entry *StorageEntry) error {
	if err := s.checkWrite(ctx); err != nil {
		return err
	}
	return s.underlying.Put(ctx, entry)
}

func (s *SnapshotStorageRouter) Delete(ctx context.Context, key string) error {
	if err := s.checkWrite(ctx); err != nil {
		return err
	}
	return s.underlying.Delete(ctx, key)
}
