package persistence

import (
	"context"
	"fmt"
	"sync"
	"testing"

	database "github.com/nexuscrm/db"
)

func TestHistorySequenceTiDBBatchReservationsRemainAtomicUnderContention(t *testing.T) {
	rawDB, cleanup := openSchemaDDLTiDB(t)
	defer cleanup()
	rawDB.SetMaxOpenConns(12)

	ctx := context.Background()
	if _, err := rawDB.ExecContext(ctx, `CREATE TABLE _System_FieldHistorySequence (
		FieldId VARCHAR(255) PRIMARY KEY,
		CurrentSequence BIGINT NOT NULL DEFAULT 0,
		CreatedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
		LastModifiedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
	)`); err != nil {
		t.Fatalf("create sequence table: %v", err)
	}

	tm := NewTransactionManager(database.NewConnectionFromDB(rawDB))
	const (
		workers    = 8
		iterations = 20
	)
	forward := []string{"field-a", "field-b", "field-c", "field-d"}
	reverse := []string{"field-d", "field-c", "field-b", "field-a"}
	errCh := make(chan error, workers)
	var wg sync.WaitGroup
	for worker := 0; worker < workers; worker++ {
		worker := worker
		wg.Add(1)
		go func() {
			defer wg.Done()
			input := forward
			if worker%2 == 1 {
				input = reverse
			}
			for iteration := 0; iteration < iterations; iteration++ {
				err := tm.WithPessimisticRetry(ctx, func(txCtx context.Context) error {
					reserved, reserveErr := ReserveHistorySequences(txCtx, tm.ExtractTx(txCtx), input)
					if reserveErr != nil {
						return reserveErr
					}
					if len(reserved) != len(forward) {
						return fmt.Errorf("reservation size = %d, want %d", len(reserved), len(forward))
					}
					return nil
				}, 5)
				if err != nil {
					errCh <- fmt.Errorf("worker %d iteration %d: %w", worker, iteration, err)
					return
				}
			}
		}()
	}
	wg.Wait()
	close(errCh)
	for err := range errCh {
		t.Fatal(err)
	}

	want := int64(workers * iterations)
	for _, fieldID := range forward {
		var got int64
		if err := rawDB.QueryRowContext(ctx,
			`SELECT CurrentSequence FROM _System_FieldHistorySequence WHERE FieldId = ?`,
			fieldID,
		).Scan(&got); err != nil {
			t.Fatalf("read %s sequence: %v", fieldID, err)
		}
		if got != want {
			t.Fatalf("%s sequence = %d, want %d", fieldID, got, want)
		}
	}
}

func TestHistorySequenceTiDBBatchedPrelockMixedRowsRemainZeroUnderContention(t *testing.T) {
	rawDB, cleanup := openSchemaDDLTiDB(t)
	defer cleanup()
	rawDB.SetMaxOpenConns(12)

	ctx := context.Background()
	if _, err := rawDB.ExecContext(ctx, `CREATE TABLE _System_FieldHistorySequence (
		FieldId VARCHAR(255) PRIMARY KEY,
		CurrentSequence BIGINT NOT NULL DEFAULT 0,
		CreatedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
		LastModifiedDate DATETIME(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
	)`); err != nil {
		t.Fatalf("create sequence table: %v", err)
	}
	if _, err := rawDB.ExecContext(ctx, `INSERT INTO _System_FieldHistorySequence
		(FieldId, CurrentSequence) VALUES ('field-a', 0), ('field-c', 0)`); err != nil {
		t.Fatalf("seed existing sequence rows: %v", err)
	}

	tm := NewTransactionManager(database.NewConnectionFromDB(rawDB))
	const (
		workers    = 8
		iterations = 20
	)
	forward := []string{"field-a", "field-b", "field-c", "field-d"}
	reverse := []string{"field-d", "field-c", "field-b", "field-a"}
	errCh := make(chan error, workers)
	var wg sync.WaitGroup
	for worker := 0; worker < workers; worker++ {
		worker := worker
		wg.Add(1)
		go func() {
			defer wg.Done()
			input := forward
			if worker%2 == 1 {
				input = reverse
			}
			for iteration := 0; iteration < iterations; iteration++ {
				err := tm.WithPessimisticRetry(ctx, func(txCtx context.Context) error {
					return prelockHistorySequenceRows(txCtx, tm.ExtractTx(txCtx), input)
				}, 5)
				if err != nil {
					errCh <- fmt.Errorf("worker %d iteration %d: %w", worker, iteration, err)
					return
				}
			}
		}()
	}
	wg.Wait()
	close(errCh)
	for err := range errCh {
		t.Fatal(err)
	}

	for _, fieldID := range forward {
		var got int64
		if err := rawDB.QueryRowContext(ctx,
			`SELECT CurrentSequence FROM _System_FieldHistorySequence WHERE FieldId = ?`,
			fieldID,
		).Scan(&got); err != nil {
			t.Fatalf("read %s sequence: %v", fieldID, err)
		}
		if got != 0 {
			t.Fatalf("%s sequence = %d, want zero after lock-only batches", fieldID, got)
		}
	}
}
