package execution

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/approval"
	executionstore "github.com/tidbcloud/AgentTeam/internal/store/role/execution"
)

func TestValidateApprovalInstanceUsesStrictDurablePayloadContract(t *testing.T) {
	for _, testCase := range []struct {
		name    string
		mutate  func(t *testing.T, instance *approval.Instance)
		wantErr bool
	}{
		{
			name: "duplicate action with canonical digest",
			mutate: func(_ *testing.T, instance *approval.Instance) {
				instance.Payload = append(
					append(append([]byte(nil), instance.Payload[:len(instance.Payload)-1]...),
						[]byte(`,"action":"create"`)...),
					'}',
				)
			},
			wantErr: true,
		},
		{
			name: "pretty payload with raw digest",
			mutate: func(t *testing.T, instance *approval.Instance) {
				var pretty bytes.Buffer
				require.NoError(t, json.Indent(&pretty, instance.Payload, "", "  "))
				instance.Payload = pretty.Bytes()
				sum := sha256.Sum256(instance.Payload)
				instance.PayloadDigest = hex.EncodeToString(sum[:])
			},
			wantErr: true,
		},
		{
			name: "typed field order with raw digest",
			mutate: func(t *testing.T, instance *approval.Instance) {
				var payload ApprovalPayload
				require.NoError(t, json.Unmarshal(instance.Payload, &payload))
				instance.Payload, _ = json.Marshal(payload)
				sum := sha256.Sum256(instance.Payload)
				instance.PayloadDigest = hex.EncodeToString(sum[:])
			},
			wantErr: true,
		},
		{
			name: "pretty payload with canonical digest",
			mutate: func(t *testing.T, instance *approval.Instance) {
				var pretty bytes.Buffer
				require.NoError(t, json.Indent(&pretty, instance.Payload, "", "  "))
				instance.Payload = pretty.Bytes()
			},
		},
		{
			name: "typed field order with canonical digest",
			mutate: func(t *testing.T, instance *approval.Instance) {
				var payload ApprovalPayload
				require.NoError(t, json.Unmarshal(instance.Payload, &payload))
				instance.Payload, _ = json.Marshal(payload)
			},
		},
	} {
		t.Run(testCase.name, func(t *testing.T) {
			instance := redApproval(t, approval.StatePending)
			testCase.mutate(t, &instance)

			err := ValidateApprovalInstance(instance)
			if testCase.wantErr {
				require.ErrorIs(t, err, ErrInvariant)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestExecuteReleasesSettleContextWhenCallReturns(t *testing.T) {
	service, probe, claim, claimNow := newExecutionProbe(
		t, RoleCreateProposal{Name: "Release", Definition: "Review releases"},
	)
	probe.applyErr = &ApplyError{Outcome: ApplyContextCancelled}
	var settleCtx context.Context
	service.dependencies.ReleaseUncertain = func(
		ctx context.Context,
		_ executionstore.ExecutionFence,
		_ time.Time,
	) error {
		require.NoError(t, ctx.Err(), "bounded detached context must be live during settle")
		settleCtx = ctx
		return nil
	}

	require.NoError(t, service.Execute(context.Background(), claim, claimNow))
	require.NotNil(t, settleCtx)
	require.ErrorIs(t, settleCtx.Err(), context.Canceled,
		"settle context must be released when Execute returns")
}
