package services

import (
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

type localStorageBackend struct {
	id   string
	root string
}

func (b localStorageBackend) ID() string   { return b.id }
func (b localStorageBackend) Type() string { return StorageBackendLocal }

func (b localStorageBackend) Put(_ context.Context, key string, body io.Reader, _ int64, _ string) error {
	path, err := b.path(key)
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return err
	}
	target, err := os.OpenFile(path, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, 0o644)
	if err != nil {
		return err
	}
	defer target.Close()
	_, err = io.Copy(target, body)
	return err
}

func (b localStorageBackend) Get(_ context.Context, key string) (StorageObject, error) {
	path, err := b.path(key)
	if err != nil {
		return StorageObject{}, err
	}
	source, err := os.Open(path)
	if err != nil {
		return StorageObject{}, err
	}
	stat, err := source.Stat()
	if err != nil {
		source.Close()
		return StorageObject{}, err
	}
	return StorageObject{Key: key, Size: stat.Size(), ModTime: stat.ModTime(), Body: source}, nil
}

func (b localStorageBackend) Delete(_ context.Context, key string) error {
	path, err := b.path(key)
	if err != nil {
		return err
	}
	if err := os.Remove(path); err != nil && !os.IsNotExist(err) {
		return err
	}
	return nil
}

func (b localStorageBackend) DeletePrefix(_ context.Context, prefix string) error {
	path, err := b.path(prefix)
	if err != nil {
		return err
	}
	if err := os.RemoveAll(path); err != nil && !os.IsNotExist(err) {
		return err
	}
	return nil
}

func (b localStorageBackend) Usage(_ context.Context) (int64, error) {
	var total int64
	err := filepath.WalkDir(b.root, func(path string, entry os.DirEntry, err error) error {
		if err != nil {
			return err
		}
		if entry.IsDir() {
			return nil
		}
		info, err := entry.Info()
		if err != nil {
			return err
		}
		total += info.Size()
		return nil
	})
	if os.IsNotExist(err) {
		return 0, nil
	}
	return total, err
}

func (b localStorageBackend) Test(ctx context.Context) error {
	key := ".warpbox-storage-test-" + randomID(6)
	if err := b.Put(ctx, key, strings.NewReader("ok"), 2, "text/plain"); err != nil {
		return err
	}
	return b.Delete(ctx, key)
}

func (b localStorageBackend) path(key string) (string, error) {
	key = filepath.Clean(strings.TrimPrefix(key, "/"))
	if key == "." || strings.HasPrefix(key, "..") || filepath.IsAbs(key) {
		return "", fmt.Errorf("invalid storage key")
	}
	path := filepath.Join(b.root, key)
	root, err := filepath.Abs(b.root)
	if err != nil {
		return "", err
	}
	abs, err := filepath.Abs(path)
	if err != nil {
		return "", err
	}
	if abs != root && !strings.HasPrefix(abs, root+string(os.PathSeparator)) {
		return "", fmt.Errorf("invalid storage key")
	}
	return abs, nil
}
