package store

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/easyai/easyai-ai-gateway/apps/api/internal/identity"
	"github.com/google/uuid"
)

func TestDisableActiveIdentityRevisionQueuesSecretsAfterRuntimeRetirementAndClearsReferences(t *testing.T) {
	db := newIdentityPairingPostgresTestStore(t)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	seedIdentityActivationPrerequisites(t, ctx, db, "default")
	revisionID := seedIdentityLifecycleRevision(t, ctx, db, identity.RevisionActive, "default")
	machineReference := "identity-machine-disable-" + uuid.NewString()
	sessionReference := "identity-session-disable-" + uuid.NewString()
	if _, err := db.pool.Exec(ctx, `UPDATE gateway_identity_configuration_revisions
SET machine_credential_ref=$2,session_encryption_key_ref=$3 WHERE id=$1::uuid`,
		revisionID, machineReference, sessionReference); err != nil {
		t.Fatalf("seed active identity Secret references: %v", err)
	}
	if err := db.QueueIdentitySecretCleanup(ctx, machineReference, time.Now().Add(-time.Minute)); err != nil {
		t.Fatalf("seed prematurely due machine Secret cleanup: %v", err)
	}

	disableStartedAt := time.Now().UTC()
	disabled, err := db.DisableActiveIdentityRevision(ctx, 1, "trace-disable-secrets", "audit-disable-secrets")
	if err != nil {
		t.Fatalf("disable active identity revision: %v", err)
	}
	if disabled.State != identity.RevisionSuperseded || disabled.Version != 2 ||
		disabled.MachineCredentialRef != "" || disabled.SessionEncryptionKeyRef != "" {
		t.Fatalf("disabled revision retained Secret references: %#v", disabled)
	}

	persisted, err := db.IdentityConfigurationRevision(ctx, revisionID)
	if err != nil {
		t.Fatalf("read disabled identity revision: %v", err)
	}
	if persisted.State != identity.RevisionSuperseded || persisted.MachineCredentialRef != "" ||
		persisted.SessionEncryptionKeyRef != "" {
		t.Fatalf("persisted disabled revision retained Secret references: %#v", persisted)
	}

	type queuedSecret struct {
		reference string
		status    string
		notBefore time.Time
	}
	rows, err := db.pool.Query(ctx, `SELECT secret_ref,status,not_before
FROM gateway_identity_secret_cleanup_queue ORDER BY secret_ref`)
	if err != nil {
		t.Fatalf("read disabled identity Secret cleanup queue: %v", err)
	}
	defer rows.Close()
	queued := make(map[string]queuedSecret, 2)
	for rows.Next() {
		var secret queuedSecret
		if err := rows.Scan(&secret.reference, &secret.status, &secret.notBefore); err != nil {
			t.Fatalf("scan disabled identity Secret cleanup: %v", err)
		}
		queued[secret.reference] = secret
	}
	if err := rows.Err(); err != nil {
		t.Fatalf("iterate disabled identity Secret cleanup queue: %v", err)
	}
	if len(queued) != 2 {
		t.Fatalf("disabled identity cleanup queue entries=%v, want both Secret references", queued)
	}
	minimumSafeCleanup := disableStartedAt.Add(45 * time.Second)
	for _, reference := range []string{machineReference, sessionReference} {
		secret, ok := queued[reference]
		if !ok || secret.status != "pending" || secret.notBefore.Before(minimumSafeCleanup) {
			t.Fatalf("queued Secret %q=%#v present=%t, want pending cleanup no earlier than %s",
				reference, secret, ok, minimumSafeCleanup)
		}
	}
	claims, err := db.ClaimIdentitySecretCleanups(ctx, 10, time.Minute)
	if err != nil {
		t.Fatalf("claim not-yet-due disabled identity Secrets: %v", err)
	}
	if len(claims) != 0 {
		t.Fatalf("disabled identity Secrets became claimable before Runtime retirement: %#v", claims)
	}
}

func TestDisableActiveIdentityRevisionCleanupConflictRollsBackStateReferencesAndQueue(t *testing.T) {
	db := newIdentityPairingPostgresTestStore(t)
	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	seedIdentityActivationPrerequisites(t, ctx, db, "default")
	revisionID := seedIdentityLifecycleRevision(t, ctx, db, identity.RevisionActive, "default")
	machineReference := "identity-machine-disable-conflict-" + uuid.NewString()
	sessionReference := "identity-session-disable-conflict-" + uuid.NewString()
	if _, err := db.pool.Exec(ctx, `UPDATE gateway_identity_configuration_revisions
SET machine_credential_ref=$2,session_encryption_key_ref=$3 WHERE id=$1::uuid`,
		revisionID, machineReference, sessionReference); err != nil {
		t.Fatalf("seed conflicting active identity Secret references: %v", err)
	}
	if err := db.QueueIdentitySecretCleanup(ctx, machineReference, time.Now().Add(-time.Minute)); err != nil {
		t.Fatalf("queue machine Secret before conflict claim: %v", err)
	}
	claims, err := db.ClaimIdentitySecretCleanups(ctx, 1, time.Minute)
	if err != nil || len(claims) != 1 || claims[0].Reference != machineReference {
		t.Fatalf("claim machine Secret before disable: claims=%#v error=%v", claims, err)
	}

	_, err = db.DisableActiveIdentityRevision(ctx, 1, "trace-disable-conflict", "audit-disable-conflict")
	if !errors.Is(err, ErrIdentitySecretCleanupConflict) {
		t.Fatalf("disable with claimed Secret cleanup error=%v, want cleanup conflict", err)
	}
	persisted, err := db.IdentityConfigurationRevision(ctx, revisionID)
	if err != nil {
		t.Fatalf("read active identity revision after cleanup conflict: %v", err)
	}
	if persisted.State != identity.RevisionActive || persisted.Version != 1 ||
		persisted.MachineCredentialRef != machineReference || persisted.SessionEncryptionKeyRef != sessionReference {
		t.Fatalf("cleanup conflict partially disabled identity revision: %#v", persisted)
	}

	var status, claimToken string
	if err := db.pool.QueryRow(ctx, `SELECT status,claim_token::text FROM gateway_identity_secret_cleanup_queue
WHERE secret_ref=$1`, machineReference).Scan(&status, &claimToken); err != nil {
		t.Fatalf("read claimed machine Secret after disable rollback: %v", err)
	}
	if status != "claimed" || claimToken != claims[0].ClaimToken {
		t.Fatalf("disable rollback changed claimed cleanup status=%q token=%q", status, claimToken)
	}
	var sessionQueueEntries int
	if err := db.pool.QueryRow(ctx, `SELECT count(*) FROM gateway_identity_secret_cleanup_queue
WHERE secret_ref=$1`, sessionReference).Scan(&sessionQueueEntries); err != nil {
		t.Fatalf("count session Secret cleanup after disable rollback: %v", err)
	}
	if sessionQueueEntries != 0 {
		t.Fatalf("disable cleanup conflict left %d partial session cleanup entries", sessionQueueEntries)
	}
}
