// Package containerfs implements storage.Provider for bot containers
// backed by host-side bind mounts. Writing to <dataRoot>/bots/<bot_id>/media/<subpath>
// on the host makes the file available at /data/media/<subpath> inside the container.
package containerfs

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

const containerMediaRoot = "/data/media"

// Provider stores media assets via the host-side bind mount path
// that maps to /data inside bot containers.
type Provider struct {
	dataRoot string
}

// New creates a container-based storage provider.
// dataRoot is the host directory that contains per-bot data (e.g. "data").
func New(dataRoot string) (*Provider, error) {
	abs, err := filepath.Abs(dataRoot)
	if err != nil {
		return nil, fmt.Errorf("resolve data root: %w", err)
	}
	return &Provider{dataRoot: abs}, nil
}

// Put writes data to the host bind mount path for the bot container.
func (p *Provider) Put(_ context.Context, key string, reader io.Reader) error {
	dest, err := p.hostPath(key)
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(dest), 0o755); err != nil {
		return fmt.Errorf("create parent dir: %w", err)
	}
	f, err := os.Create(dest)
	if err != nil {
		return fmt.Errorf("create file: %w", err)
	}
	defer f.Close()
	if _, err := io.Copy(f, reader); err != nil {
		return fmt.Errorf("write file: %w", err)
	}
	return nil
}

// Open reads a file from the host bind mount path.
func (p *Provider) Open(_ context.Context, key string) (io.ReadCloser, error) {
	dest, err := p.hostPath(key)
	if err != nil {
		return nil, err
	}
	f, err := os.Open(dest)
	if err != nil {
		return nil, fmt.Errorf("open file: %w", err)
	}
	return f, nil
}

// Delete removes a file from the host bind mount path.
func (p *Provider) Delete(_ context.Context, key string) error {
	dest, err := p.hostPath(key)
	if err != nil {
		return err
	}
	if err := os.Remove(dest); err != nil && !os.IsNotExist(err) {
		return fmt.Errorf("delete file: %w", err)
	}
	return nil
}

// AccessPath returns the container-internal path for a storage key.
// Routing key format: "<bot_id>/<storage_key>" → "/data/media/<storage_key>".
func (p *Provider) AccessPath(key string) string {
	_, sub := splitRoutingKey(key)
	return containerMediaRoot + "/" + sub
}

// hostPath converts a routing key into the host-side file path.
// Routing key format: "<bot_id>/<storage_key>" → "<dataRoot>/bots/<bot_id>/media/<storage_key>".
func (p *Provider) hostPath(key string) (string, error) {
	clean := filepath.Clean(key)
	if filepath.IsAbs(clean) {
		return "", fmt.Errorf("absolute key is forbidden: %s", key)
	}
	if strings.HasPrefix(clean, ".."+string(filepath.Separator)) || clean == ".." {
		return "", fmt.Errorf("path traversal is forbidden: %s", key)
	}
	botID, subPath := splitRoutingKey(clean)
	if strings.TrimSpace(botID) == "" || strings.TrimSpace(subPath) == "" {
		return "", fmt.Errorf("invalid storage key: %s", key)
	}
	joined := filepath.Join(p.dataRoot, "bots", botID, "media", subPath)
	if !strings.HasPrefix(joined, p.dataRoot+string(filepath.Separator)) {
		return "", fmt.Errorf("path escapes data root: %s", key)
	}
	return joined, nil
}

// OpenContainerFile opens a file from a bot's /data/ directory on the host.
// containerPath must start with "/data/".
func (p *Provider) OpenContainerFile(botID, containerPath string) (io.ReadCloser, error) {
	const dataPrefix = "/data/"
	if !strings.HasPrefix(containerPath, dataPrefix) {
		return nil, fmt.Errorf("path must start with /data/")
	}
	subPath := containerPath[len(dataPrefix):]
	if subPath == "" || strings.Contains(subPath, "..") {
		return nil, fmt.Errorf("invalid container path")
	}
	hostPath := filepath.Join(p.dataRoot, "bots", botID, subPath)
	if !strings.HasPrefix(hostPath, p.dataRoot+string(filepath.Separator)) {
		return nil, fmt.Errorf("path escapes data root")
	}
	return os.Open(hostPath)
}

// ListPrefix returns all keys under the given routing prefix.
// prefix is expected to be of the form "<bot_id>/<hash_prefix>/<hash>" (without extension).
func (p *Provider) ListPrefix(_ context.Context, prefix string) ([]string, error) {
	botID, sub := splitRoutingKey(prefix)
	if botID == "" || sub == "" {
		return nil, nil
	}
	dir := filepath.Dir(filepath.Join(p.dataRoot, "bots", botID, "media", sub))
	base := filepath.Base(sub)
	entries, err := os.ReadDir(dir)
	if err != nil {
		return nil, nil
	}
	var keys []string
	for _, e := range entries {
		if e.IsDir() {
			continue
		}
		name := e.Name()
		if strings.HasPrefix(name, base) {
			storageKey := filepath.Join(filepath.Dir(sub), name)
			keys = append(keys, filepath.Join(botID, storageKey))
		}
	}
	return keys, nil
}

// splitRoutingKey splits a routing key "<bot_id>/<storage_key>" into its parts.
func splitRoutingKey(key string) (botID, storageKey string) {
	idx := strings.IndexByte(key, filepath.Separator)
	if idx <= 0 {
		return "", key
	}
	return key[:idx], key[idx+1:]
}
