package persistence

import (
	"context"
	"crypto/sha256"
	"encoding/hex"
	"errors"
	"strings"
	"testing"
	"time"

	applicationresultsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationresultv1"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	flowcommandsemanticv3 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowcommandv3"
)

func TestRuntimePlatformCommandTrustedScopeDigestMatchesAcceptance(t *testing.T) {
	scope := RuntimePlatformCommandTrustedScope{
		OrgID: "org-1", AppInstallationID: "installation-1",
		CallerWorkloadID: "integration-1", RuntimeCapabilityID: "runtime.commands.accept.v1",
	}
	wantSum := sha256.Sum256([]byte(
		"org-1\x00installation-1\x00integration-1\x00runtime.commands.accept.v1",
	))
	want := hex.EncodeToString(wantSum[:])
	first, err := scope.Digest()
	if err != nil {
		t.Fatal(err)
	}
	second, err := scope.Digest()
	if err != nil {
		t.Fatal(err)
	}
	if first != want || second != want {
		t.Fatalf("Digest() = %q, %q, want exact acceptance digest %q", first, second, want)
	}
	principalScope := scope
	principalScope.PrincipalID = "user-1"
	principalDigest, err := principalScope.Digest()
	if err != nil {
		t.Fatal(err)
	}
	otherPrincipal := principalScope
	otherPrincipal.PrincipalID = "user-2"
	otherPrincipalDigest, err := otherPrincipal.Digest()
	if err != nil {
		t.Fatal(err)
	}
	if principalDigest == first || principalDigest == otherPrincipalDigest {
		t.Fatal("principal-scoped command partition collapsed across machine scope or principals")
	}

	invalid := []RuntimePlatformCommandTrustedScope{
		{},
		{OrgID: " org-1", AppInstallationID: "installation-1", CallerWorkloadID: "integration-1", RuntimeCapabilityID: "capability-1"},
		{OrgID: "org-1", AppInstallationID: "installation\x00one", CallerWorkloadID: "integration-1", RuntimeCapabilityID: "capability-1"},
		{OrgID: "org-1", AppInstallationID: "installation-1", CallerWorkloadID: strings.Repeat("w", runtimePlatformCommandTrustedWorkloadMaxBytes+1), RuntimeCapabilityID: "capability-1"},
		{OrgID: "org-1", AppInstallationID: "installation-1", CallerWorkloadID: "integration-1", RuntimeCapabilityID: "capability-1", PrincipalID: " user-1"},
	}
	for i, value := range invalid {
		if digest, err := value.Digest(); err == nil || digest != "" {
			t.Fatalf("invalid scope %d Digest() = %q, %v, want rejection", i, digest, err)
		}
	}
}

func TestGetApplicationResultValidatesDeadlineBeforeStorage(t *testing.T) {
	fixture := newRuntimePlatformApplicationResultFixture(t)
	canceled, cancel := context.WithTimeout(context.Background(), time.Hour)
	cancel()
	expired, expire := context.WithDeadline(context.Background(), time.Now().Add(-time.Second))
	defer expire()
	tests := []struct {
		name      string
		ctx       context.Context
		request   applicationresultsemantic.GetResultRequest
		category  commonsemantic.FailureCategory
		component commonsemantic.InvalidComponent
	}{
		{name: "nil context", request: fixture.request, category: commonsemantic.FailureInvalidRequest, component: commonsemantic.InvalidComponentDeadline},
		{name: "missing deadline", ctx: context.Background(), request: fixture.request, category: commonsemantic.FailureInvalidRequest, component: commonsemantic.InvalidComponentDeadline},
		{name: "canceled", ctx: canceled, request: fixture.request, category: commonsemantic.FailureCanceled},
		{name: "expired", ctx: expired, request: fixture.request, category: commonsemantic.FailureDeadlineExceeded},
		{name: "invalid command", ctx: runtimePlatformApplicationResultTestContext(t), request: applicationresultsemantic.GetResultRequest{
			CommandID: "", ExecutionRefDigest: fixture.request.ExecutionRefDigest,
		}, category: commonsemantic.FailureInvalidRequest, component: commonsemantic.InvalidComponentCommandData},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			_, err := (*RuntimePlatformCommandRepository)(nil).GetApplicationResult(test.ctx, fixture.scope, test.request)
			failure := requireRuntimePlatformApplicationResultFailure(t, err, test.category)
			if failure.InvalidComponent != test.component {
				t.Fatalf("InvalidComponent = %q, want %q", failure.InvalidComponent, test.component)
			}
		})
	}
}

type runtimePlatformApplicationResultFixture struct {
	scope       RuntimePlatformCommandTrustedScope
	scopeDigest string
	request     applicationresultsemantic.GetResultRequest
	payload     []byte
}

func newRuntimePlatformApplicationResultFixture(t *testing.T) runtimePlatformApplicationResultFixture {
	t.Helper()
	scope := RuntimePlatformCommandTrustedScope{
		OrgID: "org-1", AppInstallationID: "installation-1",
		CallerWorkloadID: "integration-1", RuntimeCapabilityID: "runtime.commands.accept.v1",
	}
	scopeDigest, err := scope.Digest()
	if err != nil {
		t.Fatal(err)
	}
	canonicalInputs := []byte(`{"recordId":"record-1"}`)
	inputDigest, err := flowcommandsemanticv3.ComputeInputDigest(
		canonicalInputs,
		flowcommandsemanticv3.SuppressExternalEffects,
	)
	if err != nil {
		t.Fatal(err)
	}
	payload, err := flowcommandsemanticv3.Encode(flowcommandsemanticv3.Payload{
		ExecutionRef: applicationresultsemantic.ExecutionRef{
			ExecutionID: "execution-1", PackGenerationID: "pack-generation-1",
			ServingEpochDigest: "sha256:" + strings.Repeat("a", 64),
			DefinitionRef:      "flow-1", DefinitionVersion: "1", InputDigest: inputDigest,
		},
		CanonicalInputs: canonicalInputs, ExternalEffectsMode: flowcommandsemanticv3.SuppressExternalEffects,
	})
	if err != nil {
		t.Fatal(err)
	}
	decoded, err := flowcommandsemanticv3.Decode(payload)
	if err != nil {
		t.Fatal(err)
	}
	executionRefDigest, err := applicationresultsemantic.ExecutionRefDigest(decoded.ExecutionRef)
	if err != nil {
		t.Fatal(err)
	}
	return runtimePlatformApplicationResultFixture{
		scope: scope, scopeDigest: scopeDigest, payload: payload,
		request: applicationresultsemantic.GetResultRequest{
			CommandID: "command-1", ExecutionRefDigest: executionRefDigest,
		},
	}
}

func runtimePlatformApplicationResultTestContext(t *testing.T) context.Context {
	t.Helper()
	ctx, cancel := context.WithTimeout(t.Context(), time.Second)
	t.Cleanup(cancel)
	return ctx
}

func mustRuntimePlatformApplicationCompletionEnvelope(
	t *testing.T,
	completion RuntimePlatformCommandApplicationCompletion,
) []byte {
	t.Helper()
	encoded, err := encodeRuntimePlatformCommandApplicationCompletion(completion)
	if err != nil {
		t.Fatal(err)
	}
	return encoded
}

func requireRuntimePlatformApplicationResultFailure(
	t *testing.T,
	err error,
	category commonsemantic.FailureCategory,
) *applicationresultsemantic.Failure {
	t.Helper()
	var failure *applicationresultsemantic.Failure
	if !errors.As(err, &failure) || failure.Category != category {
		t.Fatalf("error = %v, want application result failure %q", err, category)
	}
	return failure
}
