package filestore

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

// NewFileSystemStoreHTTPMiddleware returns an HTTP middleware which strips the
// base URL path prefix from incoming paths, suitable for passing to an
// appropriately-configured FileSystemStore.
func NewFileSystemStoreHTTPMiddleware(baseURL *url.URL, next http.Handler) http.Handler {
	return http.StripPrefix(baseURL.Path, next)
}

// FileSystemStore is a file store that stores files on the local filesystem.
// It is currently intended for usage in a development environment.
type FileSystemStore struct {
	rootPath string
	baseURL  *url.URL
}

// NewFileSystemStore creates a new FileSystemStore. It accepts a root path,
// which is the storage location on the local file system for stored objects,
// and a baseURL which is a URL which should be configured to serve the stored
// files over HTTP.
func NewFileSystemStore(rootPath string, baseURL *url.URL) (*FileSystemStore, error) {
	url := *baseURL
	if !strings.HasSuffix(url.Path, "/") {
		url.Path += "/"
	}
	return &FileSystemStore{rootPath: rootPath, baseURL: &url}, nil
}

// GetObject retrieves an object from the local filesystem.
func (s *FileSystemStore) GetObject(ctx context.Context, key string) (io.ReadCloser, error) {
	path := filepath.Join(s.rootPath, key)
	fptr, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("error opening file: %v", err)
	}
	return fptr, nil
}

type readCloser struct {
	io.Reader
	io.Closer
}

// GetObjectWithRange retrieves an object from the local filesystem with the given byte range.
func (s *FileSystemStore) GetObjectWithRange(ctx context.Context, key string, start, end int64) (io.ReadCloser, error) {
	path := filepath.Join(s.rootPath, key)
	fptr, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("error opening file: %v", err)
	}
	_, err = fptr.Seek(start, io.SeekStart)
	if err != nil {
		return nil, fmt.Errorf("error seeking in file: %v", err)
	}

	return readCloser{
		Reader: io.LimitReader(fptr, end-start),
		Closer: fptr,
	}, nil
}

// GetURL returns an HTTP URL for the provided file path.
func (s *FileSystemStore) GetURL(ctx context.Context, key string) (string, error) {
	var url url.URL
	url.Scheme = s.baseURL.Scheme
	url.Host = s.baseURL.Host
	url.Path = fmt.Sprintf("%s%s", s.baseURL.Path, key)
	return url.String(), nil
}

// PutObject writes an object to the local filesystem.
func (s *FileSystemStore) PutObject(ctx context.Context, key string, r io.Reader, _ string) (int64, error) {
	path := filepath.Join(s.rootPath, key)
	if err := os.MkdirAll(filepath.Dir(path), 0777); err != nil {
		return 0, fmt.Errorf("error creating directories: %v", err)
	}
	fptr, err := os.Create(path)
	if err != nil {
		return 0, fmt.Errorf("error opening file: %v", err)
	}

	n, err := io.Copy(fptr, r)
	if err != nil {
		return n, fmt.Errorf("error writing file: %v", err)
	}
	return n, nil
}
