/*
In this file we handle git lfs objects downloads and uploads
*/

package main

import (
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"io"
	"io/ioutil"
	"net/http"
	"os"
	"path/filepath"
)

var (
	errHashMismatch = errors.New("Content hash does not match OID")
	errSizeMismatch = errors.New("Content size does not match")
)

func lfsAuthorizeHandler(handleFunc serviceHandleFunc) serviceHandleFunc {
	return preAuthorizeHandler(func(w http.ResponseWriter, r *gitRequest) {

		if r.StoreLFSPath == "" {
			fail500(w, "lfsAuthorizeHandler", errors.New("Don't know where to store object, no store path specified."))
			return
		}

		if r.LfsOid == "" {
			fail500(w, "lfsAuthorizeHandler", errors.New("Lfs object oid not specified."))
			return
		}

		if err := os.MkdirAll(r.StoreLFSPath, 0700); err != nil {
			fail500(w, "Couldn't create directory for storing LFS tmp objects.", err)
			return
		}

		handleFunc(w, r)
	}, "/authorize")
}

func handleStoreLfsObject(w http.ResponseWriter, r *gitRequest) {
	file, err := ioutil.TempFile(r.StoreLFSPath, r.LfsOid)
	if err != nil {
		fail500(w, "Couldn't open tmp file for writing.", err)
		return
	}
	defer os.Remove(file.Name())
	defer file.Close()

	hash := sha256.New()
	hw := io.MultiWriter(hash, file)

	written, err := io.Copy(hw, r.Body)
	if err != nil {
		fail500(w, "Failed to save received LFS object.", err)
		return
	}
	file.Close()

	if written != r.LfsSize {
		fail500(w, "Inconsistent size: ", errSizeMismatch)
		return
	}

	shaStr := hex.EncodeToString(hash.Sum(nil))
	if shaStr != r.LfsOid {
		fail500(w, "Inconsistent size: ", errSizeMismatch)
		return
	}
	r.Header.Set("X-GitLab-Lfs-Tmp", filepath.Base(file.Name()))

	storeReq, err := r.u.newUpstreamRequest(r.Request, nil, "")
	if err != nil {
		fail500(w, "newUpstreamRequestLfsStoreCallback", err)
		return
	}

	storeResponse, err := r.u.httpClient.Do(storeReq)
	if err != nil {
		fail500(w, "doRequestLfsStoreCallback", err)
		return
	}
	defer storeResponse.Body.Close()

	forwardResponseToClient(w, storeResponse)
}