package storage

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"
)

// LocalFS 把对象写到本地文件系统的 base 之下。
type LocalFS struct {
	base string
}

// NewLocalFS 构造时校验 base 是绝对路径且存在（不存在则尝试创建）。
func NewLocalFS(base string) (*LocalFS, error) {
	if !filepath.IsAbs(base) {
		return nil, fmt.Errorf("storage: base must be absolute, got %q", base)
	}
	if err := os.MkdirAll(base, 0o755); err != nil {
		return nil, fmt.Errorf("storage: mkdir base: %w", err)
	}
	return &LocalFS{base: filepath.Clean(base)}, nil
}

// resolveSafe 把 key 限制在 base 之下，拒绝 ".." / 绝对路径 / 空 key。
func (l *LocalFS) resolveSafe(key string) (string, error) {
	// Reject empty / dot keys (would resolve to base itself and corrupt the layout)
	if key == "" || key == "." {
		return "", errors.New("storage: key must not be empty")
	}

	// Reject absolute paths
	if filepath.IsAbs(key) {
		return "", errors.New("storage: key must be relative")
	}

	// Check for .. components which could escape
	parts := strings.FieldsFunc(key, func(r rune) bool { return r == '/' || r == '\\' })
	for _, part := range parts {
		if part == ".." {
			return "", errors.New("storage: key contains ..")
		}
	}

	// Safely join and clean
	full := filepath.Join(l.base, key)
	full = filepath.Clean(full)

	// Verify it's under base by checking relative path doesn't escape
	rel, err := filepath.Rel(l.base, full)
	if err != nil || strings.HasPrefix(rel, "..") {
		return "", errors.New("storage: key escapes base")
	}

	return full, nil
}

func (l *LocalFS) Put(ctx context.Context, key string, r io.Reader, mimeType string) (PutResult, error) {
	full, err := l.resolveSafe(key)
	if err != nil {
		return PutResult{}, err
	}
	if err := os.MkdirAll(filepath.Dir(full), 0o755); err != nil {
		return PutResult{}, fmt.Errorf("storage: mkdir: %w", err)
	}
	tmp := full + ".tmp"
	f, err := os.OpenFile(tmp, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
	if err != nil {
		return PutResult{}, err
	}
	h := sha256.New()
	written, err := io.Copy(io.MultiWriter(f, h), r)
	cerr := f.Close()
	if err != nil {
		_ = os.Remove(tmp)
		return PutResult{}, err
	}
	if cerr != nil {
		_ = os.Remove(tmp)
		return PutResult{}, cerr
	}
	if err := os.Rename(tmp, full); err != nil {
		_ = os.Remove(tmp)
		return PutResult{}, err
	}
	return PutResult{
		StoragePath: filepath.ToSlash(strings.TrimPrefix(full, l.base+string(os.PathSeparator))),
		SHA256:      hex.EncodeToString(h.Sum(nil)),
		Size:        written,
	}, nil
}

func (l *LocalFS) Get(ctx context.Context, storagePath string) (io.ReadCloser, error) {
	full, err := l.resolveSafe(storagePath)
	if err != nil {
		return nil, err
	}
	f, err := os.Open(full)
	if err != nil {
		return nil, err // 显式 nil interface，避免 typed-nil 包装
	}
	return f, nil
}

func (l *LocalFS) Delete(ctx context.Context, storagePath string) error {
	full, err := l.resolveSafe(storagePath)
	if err != nil {
		return err
	}
	if err := os.Remove(full); err != nil && !os.IsNotExist(err) {
		return err
	}
	return nil
}
