// Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
// of this source code is governed by a BSD-style license that can be found in
// the LICENSE file.

package remote

import (
	"context"
	"fmt"
	"io"
	"sort"
)

// WithLogging wraps the given Storage implementation and emits logs for various
// operations.
func WithLogging(wrapped Storage, logf func(fmt string, args ...interface{})) Storage {
	return &loggingStore{
		logf:    logf,
		wrapped: wrapped,
	}
}

// loggingStore wraps a remote.Storage implementation and emits logs of the
// operations.
type loggingStore struct {
	logf    func(fmt string, args ...interface{})
	wrapped Storage
}

var _ Storage = (*loggingStore)(nil)

func (l *loggingStore) Close() error {
	l.logf("close")
	return l.wrapped.Close()
}

func (l *loggingStore) ReadObject(
	ctx context.Context, objName string,
) (_ ObjectReader, objSize int64, _ error) {
	r, size, err := l.wrapped.ReadObject(ctx, objName)
	l.logf("create reader for object %q: %s", objName, errOrPrintf(err, "%d bytes", size))
	if err != nil {
		return nil, 0, err
	}
	return &loggingReader{
		l:       l,
		name:    objName,
		wrapped: r,
	}, size, nil
}

type loggingReader struct {
	l       *loggingStore
	name    string
	wrapped ObjectReader
}

var _ ObjectReader = (*loggingReader)(nil)

func (l *loggingReader) ReadAt(ctx context.Context, p []byte, offset int64) error {
	if err := l.wrapped.ReadAt(ctx, p, offset); err != nil {
		l.l.logf("read object %q at %d (length %d): error %v", l.name, offset, len(p), err)
		return err
	}
	l.l.logf("read object %q at %d (length %d)", l.name, offset, len(p))
	return nil
}

func (l *loggingReader) Close() error {
	l.l.logf("close reader for %q", l.name)
	return l.wrapped.Close()
}

func (l *loggingStore) CreateObject(objName string) (io.WriteCloser, error) {
	l.logf("create object %q", objName)
	writer, err := l.wrapped.CreateObject(objName)
	if err != nil {
		return nil, err
	}
	return &loggingWriter{
		l:           l,
		name:        objName,
		WriteCloser: writer,
	}, nil
}

type loggingWriter struct {
	l            *loggingStore
	name         string
	bytesWritten int64
	io.WriteCloser
}

func (l *loggingWriter) Write(p []byte) (n int, err error) {
	n, err = l.WriteCloser.Write(p)
	l.bytesWritten += int64(n)
	return n, err
}

func (l *loggingWriter) Close() error {
	l.l.logf("close writer for %q after %d bytes", l.name, l.bytesWritten)
	return l.WriteCloser.Close()
}

func (l *loggingStore) List(prefix, delimiter string) ([]string, error) {
	l.logf("list (prefix=%q, delimiter=%q)", prefix, delimiter)
	res, err := l.wrapped.List(prefix, delimiter)
	if err != nil {
		return nil, err
	}
	sorted := append([]string(nil), res...)
	sort.Strings(sorted)
	for _, s := range sorted {
		l.logf(" - %s", s)
	}
	return res, nil
}

func (l *loggingStore) Delete(objName string) error {
	l.logf("delete object %q", objName)
	return l.wrapped.Delete(objName)
}

func (l *loggingStore) Size(objName string) (int64, error) {
	size, err := l.wrapped.Size(objName)
	l.logf("size of object %q: %s", objName, errOrPrintf(err, "%d", size))
	return size, err
}

func errOrPrintf(err error, format string, args ...interface{}) string {
	if err != nil {
		return fmt.Sprintf("error: %v", err)
	}
	return fmt.Sprintf(format, args...)
}

func (l *loggingStore) IsNotExistError(err error) bool {
	return l.wrapped.IsNotExistError(err)
}