// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package objects

import (
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"runtime"
)

type Interface interface {
	Save(rc io.ReadCloser) (string, string)
}

type helper struct {
	dir *string
}

func (o *helper) getDir() string {
	if o.dir == nil {
		dir, err := os.MkdirTemp("", "objectHelper")
		if err != nil {
			panic(err)
		}
		runtime.SetFinalizer(o, func(o *helper) {
			err := os.RemoveAll(dir)
			if err != nil {
				panic(err)
			}
		})
		o.dir = &dir
	}
	return *o.dir
}

func (o *helper) getPath(sha string) string {
	return filepath.Join(o.getDir(), sha[0:2], sha[2:4], sha)
}

func (o *helper) Save(rc io.ReadCloser) (string, string) {
	tempFile, err := os.CreateTemp("", "object")
	if err != nil {
		panic(err)
	}
	tempRemoved := false
	defer func() {
		if !tempRemoved {
			_ = os.Remove(tempFile.Name())
		}
	}()

	// reader
	defer rc.Close()

	// writer to file
	f, err := os.OpenFile(tempFile.Name(), os.O_CREATE|os.O_RDWR, 0o644)
	if err != nil {
		panic(err)
	}
	defer f.Close()

	// writer to sha256
	h := sha256.New()

	// copy reader to file & sha256
	w := io.MultiWriter(f, h)
	if _, err := io.Copy(w, rc); err != nil {
		panic(fmt.Errorf("while copying object: %w", err))
	}

	// finalize writer to sha256
	sha := hex.EncodeToString(h.Sum(nil))

	// finalize writer to file
	if err := tempFile.Close(); err != nil {
		panic(err)
	}
	path := o.getPath(sha)
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		panic(err)
	}
	if err := os.Rename(tempFile.Name(), path); err != nil {
		panic(err)
	}

	tempRemoved = true

	return sha, path
}

func NewObjectsHelper() Interface {
	return &helper{}
}
