package main

import (
	"bytes"
	"errors"
	"fmt"

	"github.com/git-lfs/git-lfs/v3/tools"
	"github.com/git-lfs/git-lfs/v3/tq"
)

// "upload" - all missing
func uploadAllMissing(manifest *tq.Manifest, oidsExist, oidsMissing []TestObject) error {
	retobjs, err := callBatchApi(manifest, tq.Upload, oidsMissing)

	if err != nil {
		return err
	}

	if len(retobjs) != len(oidsMissing) {
		return fmt.Errorf("incorrect number of returned objects, expected %d, got %d", len(oidsMissing), len(retobjs))
	}

	var errbuf bytes.Buffer
	for _, o := range retobjs {
		rel, _ := o.Rel("upload")
		if rel == nil {
			errbuf.WriteString(fmt.Sprintf("Missing upload link for %s\n", o.Oid))
		}
		// verify link is optional so don't check
	}

	if errbuf.Len() > 0 {
		return errors.New(errbuf.String())
	}

	return nil
}

// "upload" - all present
func uploadAllExists(manifest *tq.Manifest, oidsExist, oidsMissing []TestObject) error {
	retobjs, err := callBatchApi(manifest, tq.Upload, oidsExist)

	if err != nil {
		return err
	}

	if len(retobjs) != len(oidsExist) {
		return fmt.Errorf("incorrect number of returned objects, expected %d, got %d", len(oidsExist), len(retobjs))
	}

	var errbuf bytes.Buffer
	for _, o := range retobjs {
		link, _ := o.Rel("upload")
		if link != nil {
			errbuf.WriteString(fmt.Sprintf("Upload link should not exist for %s, was %+v\n", o.Oid, link))
		}
	}

	if errbuf.Len() > 0 {
		return errors.New(errbuf.String())
	}

	return nil
}

// "upload" - mix of missing & present
func uploadMixed(manifest *tq.Manifest, oidsExist, oidsMissing []TestObject) error {
	existSet := tools.NewStringSetWithCapacity(len(oidsExist))
	for _, o := range oidsExist {
		existSet.Add(o.Oid)
	}
	missingSet := tools.NewStringSetWithCapacity(len(oidsMissing))
	for _, o := range oidsMissing {
		missingSet.Add(o.Oid)
	}

	calloids := interleaveTestData(oidsExist, oidsMissing)
	retobjs, err := callBatchApi(manifest, tq.Upload, calloids)

	if err != nil {
		return err
	}

	count := len(oidsExist) + len(oidsMissing)
	if len(retobjs) != count {
		return fmt.Errorf("incorrect number of returned objects, expected %d, got %d", count, len(retobjs))
	}

	var errbuf bytes.Buffer
	for _, o := range retobjs {
		link, _ := o.Rel("upload")
		if existSet.Contains(o.Oid) {
			if link != nil {
				errbuf.WriteString(fmt.Sprintf("Upload link should not exist for %s, was %+v\n", o.Oid, link))
			}
		}
		if missingSet.Contains(o.Oid) && link == nil {
			errbuf.WriteString(fmt.Sprintf("Missing upload link for %s\n", o.Oid))
		}

	}

	if errbuf.Len() > 0 {
		return errors.New(errbuf.String())
	}

	return nil

}

func uploadEdgeCases(manifest *tq.Manifest, oidsExist, oidsMissing []TestObject) error {
	errorCases := make([]TestObject, 0, 5)
	errorCodeMap := make(map[string]int, 5)
	errorReasonMap := make(map[string]string, 5)
	validCases := make([]TestObject, 0, 1)
	validReasonMap := make(map[string]string, 5)

	// Invalid SHAs - code 422
	// Too short
	sha := "a345cde"
	errorCases = append(errorCases, TestObject{Oid: sha, Size: 99})
	errorCodeMap[sha] = 422
	errorReasonMap[sha] = "SHA is too short"
	// Too long
	sha = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"
	errorCases = append(errorCases, TestObject{Oid: sha, Size: 99})
	errorCodeMap[sha] = 422
	errorReasonMap[sha] = "SHA is too long"
	// Invalid characters -----!---------------------------------!
	sha = "bf3e3e2af9366a3b704ax0c31de5afa64193ebabffde2091936ad2G7510bc03a"
	errorCases = append(errorCases, TestObject{Oid: sha, Size: 99})
	errorCodeMap[sha] = 422
	errorReasonMap[sha] = "SHA contains invalid characters"

	// Invalid size - code 422
	sha = "e3bf3e2af9366a3b704af0c31de5afa64193ebabffde2091936ad237510bc03a"
	errorCases = append(errorCases, TestObject{Oid: sha, Size: -1})
	errorCodeMap[sha] = 422
	errorReasonMap[sha] = "Negative size"
	sha = "d2983e2af9366a3b704af0c31de5afa64193ebabffde2091936ad237510bc03a"
	errorCases = append(errorCases, TestObject{Oid: sha, Size: -125})
	errorCodeMap[sha] = 422
	errorReasonMap[sha] = "Negative size"

	// Zero size - should be allowed
	sha = "159f6ac723b9023b704af0c31de5afa64193ebabffde2091936ad237510bc03a"
	validCases = append(validCases, TestObject{Oid: sha, Size: 0})
	validReasonMap[sha] = "Zero size should be allowed"

	calloids := interleaveTestData(errorCases, validCases)
	retobjs, err := callBatchApi(manifest, tq.Upload, calloids)

	if err != nil {
		return err
	}

	count := len(errorCases) + len(validCases)
	if len(retobjs) != count {
		return fmt.Errorf("incorrect number of returned objects, expected %d, got %d", count, len(retobjs))
	}

	var errbuf bytes.Buffer
	for _, o := range retobjs {
		link, _ := o.Rel("upload")
		if code, iserror := errorCodeMap[o.Oid]; iserror {
			reason, _ := errorReasonMap[o.Oid]
			if link != nil {
				errbuf.WriteString(fmt.Sprintf("Upload link should not exist for %s, was %+v, reason %s\n", o.Oid, link, reason))
			}
			if o.Error == nil {
				errbuf.WriteString(fmt.Sprintf("Upload should include an error for invalid object %s, reason %s", o.Oid, reason))
			} else if o.Error.Code != code {
				errbuf.WriteString(fmt.Sprintf("Upload error code for missing object %s should be %d, got %d, reason %s\n", o.Oid, code, o.Error.Code, reason))
			}

		}
		if reason, reasonok := validReasonMap[o.Oid]; reasonok {
			if link == nil {
				errbuf.WriteString(fmt.Sprintf("Missing upload link for %s, should be present because %s\n", o.Oid, reason))
			}
		}

	}

	if errbuf.Len() > 0 {
		return errors.New(errbuf.String())
	}

	return nil

}

func init() {
	addTest("Test upload: all missing", uploadAllMissing)
	addTest("Test upload: all present", uploadAllExists)
	addTest("Test upload: mixed", uploadMixed)
	addTest("Test upload: edge cases", uploadEdgeCases)
}
