package docstore

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

	"github.com/tech/sendico/pkg/merrors"
	"github.com/tech/sendico/pkg/mlogger"
	"go.uber.org/zap"
)

type LocalStore struct {
	logger   mlogger.Logger
	rootPath string
}

func NewLocalStore(logger mlogger.Logger, cfg LocalConfig) (*LocalStore, error) {
	root := strings.TrimSpace(cfg.RootPath)
	if root == "" {
		return nil, merrors.InvalidArgument("docstore: local root_path is empty")
	}

	store := &LocalStore{
		logger:   logger.Named("docstore").Named("local"),
		rootPath: root,
	}
	store.logger.Info("Document storage initialised", zap.String("root_path", root))

	return store, nil
}

func (s *LocalStore) Save(ctx context.Context, key string, data []byte) error {
	if err := ctx.Err(); err != nil {
		return err
	}

	path, err := resolveStoragePath(s.rootPath, key)
	if err != nil {
		return err
	}

	if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
		s.logger.Warn("Failed to create document directory", zap.Error(err), zap.String("path", path))

		return err
	}
	if err := os.WriteFile(path, data, 0o600); err != nil {
		s.logger.Warn("Failed to write document file", zap.Error(err), zap.String("path", path))

		return err
	}

	return nil
}

func (s *LocalStore) Load(ctx context.Context, key string) ([]byte, error) {
	if err := ctx.Err(); err != nil {
		return nil, err
	}

	path, err := resolveStoragePath(s.rootPath, key)
	if err != nil {
		return nil, err
	}

	//nolint:gosec // path is constrained by resolveStoragePath to stay within configured root.
	data, err := os.ReadFile(path)
	if err != nil {
		s.logger.Warn("Failed to read document file", zap.Error(err), zap.String("path", path))

		return nil, err
	}

	return data, nil
}

var _ Store = (*LocalStore)(nil)

func resolveStoragePath(rootPath string, key string) (string, error) {
	cleanKey := filepath.Clean(strings.TrimSpace(key))
	if cleanKey == "" || cleanKey == "." {
		return "", merrors.InvalidArgument("docstore: key is required")
	}

	if filepath.IsAbs(cleanKey) {
		return "", merrors.InvalidArgument("docstore: absolute keys are not allowed")
	}

	rootAbs, err := filepath.Abs(rootPath)
	if err != nil {
		return "", fmt.Errorf("resolve local store root: %w", err)
	}

	path := filepath.Join(rootAbs, cleanKey)
	pathAbs, err := filepath.Abs(path)
	if err != nil {
		return "", fmt.Errorf("resolve local store path: %w", err)
	}

	prefix := rootAbs + string(filepath.Separator)
	if pathAbs != rootAbs && !strings.HasPrefix(pathAbs, prefix) {
		return "", merrors.InvalidArgument("docstore: key escapes root path")
	}

	return pathAbs, nil
}
