// Copyright (C) The Arvados Authors. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0

package blockdigest

import (
	"fmt"
	"runtime"
	"strings"
	"testing"
)

func getStackTrace() string {
	buf := make([]byte, 1000)
	bytesWritten := runtime.Stack(buf, false)
	return "Stack Trace:\n" + string(buf[:bytesWritten])
}

func expectEqual(t *testing.T, actual interface{}, expected interface{}) {
	if actual != expected {
		t.Fatalf("Expected %v but received %v instead. %s",
			expected,
			actual,
			getStackTrace())
	}
}

func expectStringSlicesEqual(t *testing.T, actual []string, expected []string) {
	if len(actual) != len(expected) {
		t.Fatalf("Expected %v (length %d), but received %v (length %d) instead. %s", expected, len(expected), actual, len(actual), getStackTrace())
	}
	for i := range actual {
		if actual[i] != expected[i] {
			t.Fatalf("Expected %v but received %v instead (first disagreement at position %d). %s", expected, actual, i, getStackTrace())
		}
	}
}

func expectValidDigestString(t *testing.T, s string) {
	bd, err := FromString(s)
	if err != nil {
		t.Fatalf("Expected %s to produce a valid BlockDigest but instead got error: %v", s, err)
	}

	expected := strings.ToLower(s)

	if expected != bd.String() {
		t.Fatalf("Expected %s to be returned by FromString(%s).String() but instead we received %s", expected, s, bd.String())
	}
}

func expectInvalidDigestString(t *testing.T, s string) {
	_, err := FromString(s)
	if err == nil {
		t.Fatalf("Expected %s to be an invalid BlockDigest, but did not receive an error", s)
	}
}

func expectBlockLocator(t *testing.T, actual BlockLocator, expected BlockLocator) {
	expectEqual(t, actual.Digest, expected.Digest)
	expectEqual(t, actual.Size, expected.Size)
	expectStringSlicesEqual(t, actual.Hints, expected.Hints)
}

func expectLocatorPatternMatch(t *testing.T, s string) {
	if !LocatorPattern.MatchString(s) {
		t.Fatalf("Expected \"%s\" to match locator pattern but it did not.",
			s)
	}
}

func expectLocatorPatternFail(t *testing.T, s string) {
	if LocatorPattern.MatchString(s) {
		t.Fatalf("Expected \"%s\" to fail locator pattern but it passed.",
			s)
	}
}

func TestValidDigestStrings(t *testing.T) {
	expectValidDigestString(t, "01234567890123456789abcdefabcdef")
	expectValidDigestString(t, "01234567890123456789ABCDEFABCDEF")
	expectValidDigestString(t, "01234567890123456789AbCdEfaBcDeF")
}

func TestInvalidDigestStrings(t *testing.T) {
	expectInvalidDigestString(t, "01234567890123456789abcdefabcdeg")
	expectInvalidDigestString(t, "01234567890123456789abcdefabcde")
	expectInvalidDigestString(t, "01234567890123456789abcdefabcdefa")
	expectInvalidDigestString(t, "g1234567890123456789abcdefabcdef")
}

func TestBlockDigestWorksAsMapKey(t *testing.T) {
	m := make(map[BlockDigest]int)
	bd, err := FromString("01234567890123456789abcdefabcdef")
	if err != nil {
		t.Fatalf("Unexpected error during FromString for block: %v", err)
	}
	m[bd] = 5
}

func TestBlockDigestGetsPrettyPrintedByPrintf(t *testing.T) {
	input := "01234567890123456789abcdefabcdef"
	fromString, err := FromString(input)
	if err != nil {
		t.Fatalf("Unexpected error during FromString: %v", err)
	}
	prettyPrinted := fmt.Sprintf("%v", fromString)
	if prettyPrinted != input {
		t.Fatalf("Expected blockDigest produced from \"%s\" to be printed as "+
			"\"%s\", but instead it was printed as %s",
			input, input, prettyPrinted)
	}
}

func TestBlockDigestGetsPrettyPrintedByPrintfInNestedStructs(t *testing.T) {
	input, err := FromString("01234567890123456789abcdefabcdef")
	if err != nil {
		t.Fatalf("Unexpected error during FromString for block: %v", err)
	}
	value := 42
	nested := struct {
		// Fun trivia fact: If this field was called "digest" instead of
		// "Digest", then it would not be exported and String() would
		// never get called on it and our output would look very
		// different.
		Digest BlockDigest
		value  int
	}{
		input,
		value,
	}
	prettyPrinted := fmt.Sprintf("%+v", nested)
	expected := fmt.Sprintf("{Digest:%s value:%d}", input, value)
	if prettyPrinted != expected {
		t.Fatalf("Expected blockDigest produced from \"%s\" to be printed as "+
			"\"%s\", but instead it was printed as %s",
			input, expected, prettyPrinted)
	}
}

func TestLocatorPatternBasic(t *testing.T) {
	expectLocatorPatternMatch(t, "12345678901234567890123456789012+12345")
	expectLocatorPatternMatch(t, "A2345678901234abcdefababdeffdfdf+12345")
	expectLocatorPatternMatch(t, "12345678901234567890123456789012+12345+A1")
	expectLocatorPatternMatch(t,
		"12345678901234567890123456789012+12345+A1+B123wxyz@_-")
	expectLocatorPatternMatch(t,
		"12345678901234567890123456789012+12345+A1+B123wxyz@_-+C@")
	expectLocatorPatternMatch(t, "12345678901234567890123456789012+12345+A")
	expectLocatorPatternMatch(t, "12345678901234567890123456789012+12345+A1+B")
	expectLocatorPatternMatch(t, "12345678901234567890123456789012+12345+A+B2")

	expectLocatorPatternFail(t, "12345678901234567890123456789012")
	expectLocatorPatternFail(t, "12345678901234567890123456789012+")
	expectLocatorPatternFail(t, "12345678901234567890123456789012+12345+")
	expectLocatorPatternFail(t, "1234567890123456789012345678901+12345")
	expectLocatorPatternFail(t, "123456789012345678901234567890123+12345")
	expectLocatorPatternFail(t, "g2345678901234abcdefababdeffdfdf+12345")
	expectLocatorPatternFail(t, "12345678901234567890123456789012+12345 ")
	expectLocatorPatternFail(t, "12345678901234567890123456789012+12345+1")
	expectLocatorPatternFail(t, "12345678901234567890123456789012+12345+1A")
	expectLocatorPatternFail(t, "12345678901234567890123456789012+12345+a1")
	expectLocatorPatternFail(t, "12345678901234567890123456789012+12345+A1+")

}

func TestParseBlockLocatorSimple(t *testing.T) {
	b, err := ParseBlockLocator("365f83f5f808896ec834c8b595288735+2310+K@qr1hi+Af0c9a66381f3b028677411926f0be1c6282fe67c@542b5ddf")
	if err != nil {
		t.Fatalf("Unexpected error parsing block locator: %v", err)
	}
	d, err := FromString("365f83f5f808896ec834c8b595288735")
	if err != nil {
		t.Fatalf("Unexpected error during FromString for block: %v", err)
	}
	expectBlockLocator(t, b, BlockLocator{Digest: d,
		Size: 2310,
		Hints: []string{"K@qr1hi",
			"Af0c9a66381f3b028677411926f0be1c6282fe67c@542b5ddf"}})
}
