package persistence

import (
	"context"
	"database/sql"
	"errors"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	applicationresultv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/applicationresultv1"
	flowcommandv3 "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/flowcommandv3"
)

const runtimePlatformCommandApplicationEffectLockTestSQL = `SELECT Id, OrgId, AppInstallationId, CallerWorkloadId,
       RuntimeCapabilityId, MetadataBinding, ExecutionScopeDigest, ScopeDigest,
       CommandId, IdempotencyKey, CommandKind, COALESCE(PartitionKey, ''),
       COALESCE(SerializationKeyDigest, ''), PayloadFormatVersion, RequestDigest,
       Payload, Receipts, State, COALESCE(LeaseToken, ''), Attempt,
       COALESCE(CAST(OwnershipEpoch AS CHAR), '0'), LeaseExpiresAt
FROM _System_RuntimePlatformCommand
WHERE Id = ? AND OrgId = ? AND AppInstallationId = ? AND RuntimeCapabilityId = ?
FOR UPDATE`

const runtimePlatformCommandApplicationCompleteTestSQL = `UPDATE _System_RuntimePlatformCommand
SET State = 'COMPLETED', ResultPayload = ?, LastError = NULL,
    DependencyKind = NULL, DependencyObjectAPIName = NULL, DependencyRecordId = NULL,
    DependencyKeyHash = NULL, DependencyConnectorOwnerKey = NULL,
    DependencyConnectorNamespace = NULL, DependencyConnectorSourceObject = NULL,
    DependencyDestinationSlotId = NULL, DependencyConnectorRemoteId = NULL,
    DependencyBlockedAt = NULL,
    LastModifiedDate = UTC_TIMESTAMP(6)
WHERE Id = ? AND OrgId = ? AND AppInstallationId = ? AND RuntimeCapabilityId = ?
  AND State = 'PROCESSING' AND LeaseToken = ? AND Attempt = ? AND OwnershipEpoch = ?`

const runtimePlatformCommandFailedApplicationLockTestSQL = `SELECT CommandId, CommandKind, PayloadFormatVersion, Payload,
       COALESCE(LeaseToken, ''), Attempt, COALESCE(CAST(OwnershipEpoch AS CHAR), '0'), LeaseExpiresAt
FROM _System_RuntimePlatformCommand
WHERE Id = ? AND OrgId = ? AND AppInstallationId = ? AND RuntimeCapabilityId = ?
  AND State = 'PROCESSING'
FOR UPDATE`

const runtimePlatformCommandFailedApplicationCompleteTestSQL = `UPDATE _System_RuntimePlatformCommand
SET State = 'FAILED', ResultPayload = ?, AvailableAt = UTC_TIMESTAMP(6),
    LeaseToken = NULL, LeaseExpiresAt = NULL, OwnershipEpoch = NULL,
    DependencyKind = NULL, DependencyObjectAPIName = NULL, DependencyRecordId = NULL,
    DependencyKeyHash = NULL, DependencyConnectorOwnerKey = NULL,
    DependencyConnectorNamespace = NULL, DependencyConnectorSourceObject = NULL,
    DependencyDestinationSlotId = NULL, DependencyConnectorRemoteId = NULL,
    DependencyBlockedAt = NULL, LastError = NULL, LastModifiedDate = UTC_TIMESTAMP(6)
WHERE Id = ? AND OrgId = ? AND AppInstallationId = ? AND RuntimeCapabilityId = ?
  AND State = 'PROCESSING' AND LeaseToken = ? AND Attempt = ? AND OwnershipEpoch = ?`

type runtimePlatformCommandApplicationResultTestTxKey struct{}

// runtimePlatformCommandApplicationResultTestTransactionOwner keeps the test
// transaction real: ExecuteApplication must recover the same *sql.Tx from the
// callback context that this owner commits or rolls back.
type runtimePlatformCommandApplicationResultTestTransactionOwner struct {
	db *sql.DB
}

func (o *runtimePlatformCommandApplicationResultTestTransactionOwner) ExtractTx(ctx context.Context) interface{} {
	if ctx == nil {
		return nil
	}
	return ctx.Value(runtimePlatformCommandApplicationResultTestTxKey{})
}

func (o *runtimePlatformCommandApplicationResultTestTransactionOwner) RunInPessimisticTransactionWithOutcome(
	ctx context.Context,
	fn func(context.Context) error,
) (ports.TransactionCompletionOutcome, error) {
	tx, err := o.db.BeginTx(ctx, nil)
	if err != nil {
		return ports.TransactionCompletionCommitError, err
	}
	txCtx := context.WithValue(ctx, runtimePlatformCommandApplicationResultTestTxKey{}, tx)
	if err := fn(txCtx); err != nil {
		if rollbackErr := tx.Rollback(); rollbackErr != nil {
			return ports.TransactionCompletionRollbackError, errors.Join(err, rollbackErr)
		}
		return ports.TransactionCompletionRolledBack, err
	}
	if err := tx.Commit(); err != nil {
		return ports.TransactionCompletionCommitError, err
	}
	return ports.TransactionCompletionCommitted, nil
}

func mustRuntimePlatformCommandApplicationResultFlowPayload(t *testing.T) []byte {
	t.Helper()
	inputs := []byte(`{"record":{"id":"001"}}`)
	inputDigest, err := flowcommandv3.ComputeInputDigest(inputs, flowcommandv3.SuppressExternalEffects)
	if err != nil {
		t.Fatalf("flowcommandv3.ComputeInputDigest(): %v", err)
	}
	payload, err := flowcommandv3.Encode(flowcommandv3.Payload{
		ExecutionRef: applicationresultv1.ExecutionRef{
			ExecutionID:        "execution-application-result-1",
			PackGenerationID:   "pack-generation-application-result-1",
			ServingEpochDigest: "sha256:" + strings.Repeat("c", 64),
			DefinitionRef:      "flow.application-result-test",
			DefinitionVersion:  "flow-version-application-result-1",
			InputDigest:        inputDigest,
		},
		CanonicalInputs:     inputs,
		ExternalEffectsMode: flowcommandv3.SuppressExternalEffects,
	})
	if err != nil {
		t.Fatalf("flowcommandv3.Encode(): %v", err)
	}
	return payload
}

func mustRuntimePlatformCommandApplicationResultStorageValue(
	t *testing.T,
	completion RuntimePlatformCommandApplicationCompletion,
) string {
	t.Helper()
	encoded, err := encodeRuntimePlatformCommandApplicationCompletion(completion)
	if err != nil {
		t.Fatalf("encodeRuntimePlatformCommandApplicationCompletion(): %v", err)
	}
	return encodeRuntimePlatformCommandResult(encoded)
}
