package records

import (
	"context"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	"github.com/nexuscrm/kernel/pkg/recordlocks"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

func TestRequireInitialHistoryBusinessRowPrelocked(t *testing.T) {
	ctx := ports.WithInitialHistoryBusinessRowObjects(context.Background(), []string{"Account"})
	ctx = recordlocks.WithScope(ctx)

	if err := requireInitialHistoryBusinessRowPrelocked(ctx, "Contact", "contact-1"); err != nil {
		t.Fatalf("unplanned object: %v", err)
	}
	if err := requireInitialHistoryBusinessRowPrelocked(ctx, "Account", "account-1"); err == nil || !strings.Contains(err.Error(), "complete business-row prelock set") {
		t.Fatalf("unlocked planned row error = %v, want coverage rejection", err)
	}

	scope := recordlocks.ScopeFromContext(ctx)
	scope.MarkAcquired(recordlocks.ParentKey{ObjectName: "Account", RecordID: "account-1"})
	if err := requireInitialHistoryBusinessRowPrelocked(ctx, "account", "account-1"); err != nil {
		t.Fatalf("prelocked row: %v", err)
	}

	scope.MarkTransactionOwned(recordlocks.ParentKey{ObjectName: "Account", RecordID: "account-new"})
	if err := requireInitialHistoryBusinessRowPrelocked(ctx, "Account", "account-new"); err != nil {
		t.Fatalf("transaction-owned row: %v", err)
	}
}

func TestInitialHistoryPlanRejectsUncoveredParentBeforeLock(t *testing.T) {
	lockCalls := 0
	repo := &stubUpsertRecordRepo{
		getLockFn: func(_ context.Context, _ string, id string) (models.SObject, error) {
			lockCalls++
			return models.SObject{constants.FieldID: id}, nil
		},
	}
	ps, _ := newAdmittedParentLockTestService(repo)
	ctx := ports.WithInitialHistorySequenceLocks(parentLaneAdmissionTestContext(), []string{"fld_contact_systemmodstamp"})
	ctx = ports.WithInitialHistoryBusinessRowObjects(ctx, []string{"Account"})

	err := ps.RunWithParentLockCoordinator(ctx, func(coordinatorCtx context.Context) error {
		_, lockErr := ps.lockInitialParentRows(coordinatorCtx, []recordlocks.ParentKey{{
			ObjectName: "Account",
			RecordID:   "account-1",
		}})
		return lockErr
	})
	if err == nil || !strings.Contains(err.Error(), "complete business-row prelock set") {
		t.Fatalf("uncovered parent error = %v, want coverage rejection", err)
	}
	if lockCalls != 0 {
		t.Fatalf("business-row lock calls = %d, want rejection before locking", lockCalls)
	}

	err = ps.RunWithParentLockCoordinator(ctx, func(coordinatorCtx context.Context) error {
		key := recordlocks.ParentKey{ObjectName: "Account", RecordID: "account-1"}
		if _, lockErr := ps.LockParentRows(coordinatorCtx, []recordlocks.ParentKey{key}); lockErr != nil {
			return lockErr
		}
		_, lockErr := ps.lockInitialParentRows(coordinatorCtx, []recordlocks.ParentKey{key})
		return lockErr
	})
	if err != nil {
		t.Fatalf("declared parent prelock: %v", err)
	}
	if lockCalls != 1 {
		t.Fatalf("business-row lock calls = %d, want one initial prelock", lockCalls)
	}
}

func TestInitialHistoryPlanAllowsParentOutsideCoveredObjects(t *testing.T) {
	lockCalls := 0
	repo := &stubUpsertRecordRepo{
		getLockFn: func(_ context.Context, _ string, id string) (models.SObject, error) {
			lockCalls++
			return models.SObject{constants.FieldID: id}, nil
		},
	}
	ps, _ := newAdmittedParentLockTestService(repo)
	ctx := ports.WithInitialHistorySequenceLocks(parentLaneAdmissionTestContext(), []string{"fld_account_name"})
	ctx = ports.WithInitialHistoryBusinessRowObjects(ctx, []string{"Account"})

	err := ps.RunWithParentLockCoordinator(ctx, func(coordinatorCtx context.Context) error {
		_, lockErr := ps.lockInitialParentRows(coordinatorCtx, []recordlocks.ParentKey{{
			ObjectName: "Contact",
			RecordID:   "contact-1",
		}})
		return lockErr
	})
	if err != nil {
		t.Fatalf("uncovered object parent: %v", err)
	}
	if lockCalls != 1 {
		t.Fatalf("business-row lock calls = %d, want one dynamic parent lock", lockCalls)
	}
}

func TestHistoryPlannedUpdatesRejectUncoveredTargetBeforeAnyNewRowLock(t *testing.T) {
	for _, test := range []struct {
		file       string
		laterStage string
	}{
		{file: "persistence_update.go", laterStage: "ps.prelockInitialUpdateParents("},
		{file: "persistence_prepared_update.go", laterStage: "ps.prelockInitialPreparedUpdateParents("},
		{file: "persistence_delete.go", laterStage: "ps.prelockDeleteParentRows("},
	} {
		source := readRecordsSourceForRawAccessStructure(t, test.file)
		guard := strings.Index(source, "requireInitialHistoryBusinessRowPrelocked(txCtx, objectName, id)")
		later := strings.Index(source, test.laterStage)
		if guard < 0 || later < 0 || guard >= later {
			t.Fatalf("%s history row coverage order changed: guard=%d later=%d", test.file, guard, later)
		}
	}
}
