package catalogsystemprotocol

import (
	"strings"
	"testing"

	systemauthorityv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/systemlayoutbundleauthorityv1"
)

const testCommandID = systemauthorityv1.CommandID(
	"018f0000-0000-7000-9000-000000000001",
)

func TestOutcomeDigestCanonicalVector(t *testing.T) {
	digest, err := OutcomeDigest(
		testCommandID,
		testDigest("1"),
		"system-principal",
		testDigest("2"),
		ResultApplied,
	)
	if err != nil {
		t.Fatal(err)
	}
	const expected = "sha256:dde3846b3a470ab328d2bd950456f14bae78130ae706e828b76d34f66619d5a8"
	if digest != expected {
		t.Fatalf("OutcomeDigest = %q, want %q", digest, expected)
	}
}

func TestOutcomeDigestBindsEveryField(t *testing.T) {
	baseline, err := OutcomeDigest(
		testCommandID,
		testDigest("1"),
		"system-principal",
		testDigest("2"),
		ResultApplied,
	)
	if err != nil {
		t.Fatal(err)
	}
	tests := map[string]struct {
		commandID     systemauthorityv1.CommandID
		requestDigest string
		principalID   string
		auditDigest   string
		result        Result
	}{
		"command": {
			commandID:     "018f0000-0000-7000-9000-000000000002",
			requestDigest: testDigest("1"),
			principalID:   "system-principal",
			auditDigest:   testDigest("2"),
			result:        ResultApplied,
		},
		"request": {
			commandID:     testCommandID,
			requestDigest: testDigest("3"),
			principalID:   "system-principal",
			auditDigest:   testDigest("2"),
			result:        ResultApplied,
		},
		"principal": {
			commandID:     testCommandID,
			requestDigest: testDigest("1"),
			principalID:   "other-principal",
			auditDigest:   testDigest("2"),
			result:        ResultApplied,
		},
		"audit": {
			commandID:     testCommandID,
			requestDigest: testDigest("1"),
			principalID:   "system-principal",
			auditDigest:   testDigest("4"),
			result:        ResultApplied,
		},
		"result": {
			commandID:     testCommandID,
			requestDigest: testDigest("1"),
			principalID:   "system-principal",
			auditDigest:   testDigest("2"),
			result:        ResultExpectedPriorMismatch,
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			digest, err := OutcomeDigest(
				test.commandID,
				test.requestDigest,
				test.principalID,
				test.auditDigest,
				test.result,
			)
			if err != nil {
				t.Fatal(err)
			}
			if digest == baseline {
				t.Fatal("field mutation retained the outcome digest")
			}
		})
	}
}

func TestClosedResultValidation(t *testing.T) {
	for _, result := range []Result{
		ResultApplied,
		ResultExpectedPriorMismatch,
		ResultAuthorityStateConflict,
	} {
		if err := ValidateResult(result); err != nil {
			t.Fatalf("ValidateResult(%q) = %v", result, err)
		}
	}
	if err := ValidateResult("RETRY"); err == nil {
		t.Fatal("unknown result was accepted")
	}
}

func TestPrincipalAndDigestValidation(t *testing.T) {
	if err := ValidatePrincipal("system-principal", testDigest("1")); err != nil {
		t.Fatal(err)
	}
	for name, principal := range map[string]string{
		"empty":       "",
		"leading":     " system-principal",
		"trailing":    "system-principal ",
		"invalid UTF": string([]byte{0xff}),
		"too long":    strings.Repeat("p", 256),
	} {
		t.Run(name, func(t *testing.T) {
			if err := ValidatePrincipal(principal, testDigest("1")); err == nil {
				t.Fatal("invalid principal was accepted")
			}
		})
	}
	if err := ValidatePrincipal("system-principal", "not-a-digest"); err == nil {
		t.Fatal("invalid audit digest was accepted")
	}

	for name, digest := range map[string]string{
		"absent prefix": strings.Repeat("1", 64),
		"uppercase":     "sha256:" + strings.Repeat("A", 64),
		"invalid hex":   "sha256:" + strings.Repeat("g", 64),
		"short":         "sha256:" + strings.Repeat("1", 63),
	} {
		t.Run("digest "+name, func(t *testing.T) {
			if err := ValidateDigest(digest); err == nil {
				t.Fatal("invalid digest was accepted")
			}
		})
	}
}

func TestOutcomeDigestRejectsInvalidInputs(t *testing.T) {
	tests := map[string]struct {
		commandID     systemauthorityv1.CommandID
		requestDigest string
		principalID   string
		auditDigest   string
		result        Result
	}{
		"command": {
			commandID:     "not-a-command",
			requestDigest: testDigest("1"),
			principalID:   "system-principal",
			auditDigest:   testDigest("2"),
			result:        ResultApplied,
		},
		"request": {
			commandID:     testCommandID,
			requestDigest: "not-a-digest",
			principalID:   "system-principal",
			auditDigest:   testDigest("2"),
			result:        ResultApplied,
		},
		"principal": {
			commandID:     testCommandID,
			requestDigest: testDigest("1"),
			principalID:   "",
			auditDigest:   testDigest("2"),
			result:        ResultApplied,
		},
		"audit": {
			commandID:     testCommandID,
			requestDigest: testDigest("1"),
			principalID:   "system-principal",
			auditDigest:   "not-a-digest",
			result:        ResultApplied,
		},
		"result": {
			commandID:     testCommandID,
			requestDigest: testDigest("1"),
			principalID:   "system-principal",
			auditDigest:   testDigest("2"),
			result:        "RETRY",
		},
	}
	for name, test := range tests {
		t.Run(name, func(t *testing.T) {
			if _, err := OutcomeDigest(
				test.commandID,
				test.requestDigest,
				test.principalID,
				test.auditDigest,
				test.result,
			); err == nil {
				t.Fatal("invalid outcome input was accepted")
			}
		})
	}
}

func testDigest(character string) string {
	return "sha256:" + strings.Repeat(character, 64)
}
