// Copyright 2020 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package lfsutil

import (
	"io"
	"os"
	"path/filepath"

	"github.com/pkg/errors"

	"gogs.io/gogs/internal/osutil"
)

var ErrObjectNotExist = errors.New("Object does not exist")

// Storager is an storage backend for uploading and downloading LFS objects.
type Storager interface {
	// Storage returns the name of the storage backend.
	Storage() Storage
	// Upload reads content from the io.ReadCloser and uploads as given oid.
	// The reader is closed once upload is finished. ErrInvalidOID is returned
	// if the given oid is not valid.
	Upload(oid OID, rc io.ReadCloser) (int64, error)
	// Download streams content of given oid to the io.Writer. It is caller's
	// responsibility the close the writer when needed. ErrObjectNotExist is
	// returned if the given oid does not exist.
	Download(oid OID, w io.Writer) error
}

// Storage is the storage type of an LFS object.
type Storage string

const (
	StorageLocal Storage = "local"
)

var _ Storager = (*LocalStorage)(nil)

// LocalStorage is a LFS storage backend on local file system.
type LocalStorage struct {
	// The root path for storing LFS objects.
	Root string
}

func (s *LocalStorage) Storage() Storage {
	return StorageLocal
}

func (s *LocalStorage) storagePath(oid OID) string {
	if len(oid) < 2 {
		return ""
	}

	return filepath.Join(s.Root, string(oid[0]), string(oid[1]), string(oid))
}

func (s *LocalStorage) Upload(oid OID, rc io.ReadCloser) (int64, error) {
	if !ValidOID(oid) {
		return 0, ErrInvalidOID
	}

	var err error
	fpath := s.storagePath(oid)
	defer func() {
		rc.Close()

		if err != nil {
			_ = os.Remove(fpath)
		}
	}()

	err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm)
	if err != nil {
		return 0, errors.Wrap(err, "create directories")
	}
	w, err := os.Create(fpath)
	if err != nil {
		return 0, errors.Wrap(err, "create file")
	}
	defer w.Close()

	written, err := io.Copy(w, rc)
	if err != nil {
		return 0, errors.Wrap(err, "copy file")
	}
	return written, nil
}

func (s *LocalStorage) Download(oid OID, w io.Writer) error {
	fpath := s.storagePath(oid)
	if !osutil.IsFile(fpath) {
		return ErrObjectNotExist
	}

	r, err := os.Open(fpath)
	if err != nil {
		return errors.Wrap(err, "open file")
	}
	defer r.Close()

	_, err = io.Copy(w, r)
	if err != nil {
		return errors.Wrap(err, "copy file")
	}
	return nil
}
