package controlplane

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

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/kernel/pkg/formula"
	domainschema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

func TestCaptureApplyAllSourceRevisionAdvanceSnapshotsBindsExactAttemptWithoutEffects(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	db.SetMaxOpenConns(1)
	attempt, target := testApplyAllSourceRevisionAdvanceSnapshotAttempt(t, db, mock)
	expectApplyAllSourceRevisionAdvanceSnapshotReads(mock, target, "")

	bundle, err := formula.ParseFunctionBundlePayload([]byte(`{
		"functions":[{
			"name":"SNAPSHOT_SENTINEL","minArgs":1,"maxArgs":1,
			"return":{"kind":"fixed","type":"string"},
			"args":[{"type":"string","nullable":true}],
			"evaluator":{"kind":"formula_expr_v1","params":["value"],"body":"value"}
		}]
	}`))
	if err != nil {
		t.Fatalf("parse runtime sentinel: %v", err)
	}
	if err := formula.RegisterRuntimeFunctionBundles(nil); err != nil {
		t.Fatalf("clear runtime functions before sentinel: %v", err)
	}
	if err := formula.RegisterRuntimeFunctionBundles([]formula.FunctionBundle{bundle}); err != nil {
		t.Fatalf("register runtime sentinel: %v", err)
	}
	t.Cleanup(func() { _ = formula.RegisterRuntimeFunctionBundles(nil) })

	snapshots, err := captureApplyAllSourceRevisionAdvanceSnapshots(
		context.Background(), attempt,
	)
	if err != nil {
		t.Fatalf("capture exact source-backed snapshots: %v", err)
	}
	state, err := requireApplyAllSourceRevisionAdvanceSnapshots(snapshots)
	if err != nil {
		t.Fatalf("require exact source-backed snapshots: %v", err)
	}
	identity := snapshots.identity
	if identity == nil || state.attempt.identity != attempt.identity ||
		state.attempt.target != target || state.attempt.database != db ||
		identity.selectedAtomClaim != attempt.identity.selectedAtomClaim ||
		identity.selectedAtomClaim != identity.provenance.selectedAtomClaim ||
		identity.selectedAtomClaim != snapshots.witness.selectedAtomClaim ||
		state.childCloneRules == nil || state.exclusiveSiblingFields == nil || state.connectedApps == nil ||
		state.childCloneRules != identity.provenance.childCloneRules ||
		state.exclusiveSiblingFields != identity.provenance.exclusiveSiblingFields ||
		state.connectedApps != identity.provenance.connectedApps {
		t.Fatalf("snapshot carrier did not retain exact attempt and snapshot identities: %#v", identity)
	}
	registry, err := formula.ActiveRuntimeExecutableFunctionRegistry()
	if err != nil {
		t.Fatalf("active runtime registry: %v", err)
	}
	if _, ok := registry.LookupFunctionDefinition("SNAPSHOT_SENTINEL"); !ok {
		t.Fatal("snapshot capture changed process-local runtime registration")
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("snapshot capture performed an unexpected SQL effect: %v", err)
	}

	mutations := []struct {
		name   string
		mutate func(*applyAllSourceRevisionAdvanceSnapshotsIdentity)
	}{
		{name: "attempt identity", mutate: func(identity *applyAllSourceRevisionAdvanceSnapshotsIdentity) {
			identity.attempt = applyAllSourceRevisionAdvanceAttempt{}
		}},
		{name: "ChildCloneRules snapshot", mutate: func(identity *applyAllSourceRevisionAdvanceSnapshotsIdentity) {
			identity.childCloneRules = &packs.ObjectSpecChildCloneRulePackOwnershipSnapshot{}
		}},
		{name: "ExclusiveSiblingFields snapshot", mutate: func(identity *applyAllSourceRevisionAdvanceSnapshotsIdentity) {
			identity.exclusiveSiblingFields = &packs.ObjectSpecExclusiveSiblingFieldPackOwnershipSnapshot{}
		}},
		{name: "ConnectedApp snapshot", mutate: func(identity *applyAllSourceRevisionAdvanceSnapshotsIdentity) {
			identity.connectedApps = &packs.ConnectedAppPackSnapshot{}
		}},
		{name: "coordinated ChildCloneRules substitution", mutate: func(identity *applyAllSourceRevisionAdvanceSnapshotsIdentity) {
			replacement := &packs.ObjectSpecChildCloneRulePackOwnershipSnapshot{}
			identity.childCloneRules = replacement
			identity.provenance.childCloneRules = replacement
		}},
		{name: "coordinated attempt retarget", mutate: func(identity *applyAllSourceRevisionAdvanceSnapshotsIdentity) {
			replacement := *identity.attempt.identity
			identity.attempt.identity = &replacement
			identity.provenance.attemptIdentity = &replacement
		}},
		{name: "coordinated selected-atom claim substitution", mutate: func(identity *applyAllSourceRevisionAdvanceSnapshotsIdentity) {
			replacement := &applyAllSourceRevisionAdvanceSelectedAtomClaim{}
			identity.selectedAtomClaim = replacement
			identity.provenance.selectedAtomClaim = replacement
		}},
	}
	for _, mutation := range mutations {
		t.Run(mutation.name, func(t *testing.T) {
			changed := testApplyAllSourceRevisionAdvanceSnapshotMutation(snapshots, mutation.mutate)
			if _, err := requireApplyAllSourceRevisionAdvanceSnapshots(changed); err == nil {
				t.Fatal("substituted snapshot carrier passed identity validation")
			}
		})
	}
}

func TestCaptureApplyAllSourceRevisionAdvanceSnapshotsFailsClosedInAcquisitionOrder(t *testing.T) {
	tests := []struct {
		stage string
		want  string
	}{
		{stage: "child", want: "ChildCloneRules"},
		{stage: "exclusive", want: "ExclusiveSiblingFields"},
		{stage: "connected", want: "ConnectedApp"},
	}
	for _, test := range tests {
		t.Run(test.stage, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatalf("sqlmock: %v", err)
			}
			t.Cleanup(func() { _ = db.Close() })
			db.SetMaxOpenConns(1)
			attempt, target := testApplyAllSourceRevisionAdvanceSnapshotAttempt(t, db, mock)
			expectApplyAllSourceRevisionAdvanceSnapshotReads(mock, target, test.stage)

			snapshots, err := captureApplyAllSourceRevisionAdvanceSnapshots(
				context.Background(), attempt,
			)
			if err == nil || !strings.Contains(err.Error(), test.want) || snapshots.identity != nil {
				t.Fatalf("capture at %s = %#v, %v, want zero carrier and %s error", test.stage, snapshots, err, test.want)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("capture at %s did not stop in order: %v", test.stage, err)
			}
		})
	}
}

func TestCaptureApplyAllSourceRevisionAdvanceSnapshotsRejectsInvalidAttemptBeforeReads(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	db.SetMaxOpenConns(1)
	valid, _ := testApplyAllSourceRevisionAdvanceSnapshotAttempt(t, db, mock)

	tests := []struct {
		name    string
		attempt applyAllSourceRevisionAdvanceAttempt
		want    string
	}{
		{name: "missing identity", want: "attempt is not an admission-bound capability"},
		{name: "unminted", attempt: testApplyAllSourceRevisionAdvanceFailureAttemptMutation(valid, func(identity *applyAllSourceRevisionAdvanceAttemptIdentity) {
			identity.minted = false
		}), want: "attempt was not minted after its exact begin transition"},
		{name: "coordinated option mutation", attempt: testApplyAllSourceRevisionAdvanceFailureAttemptMutation(valid, func(identity *applyAllSourceRevisionAdvanceAttemptIdentity) {
			identity.applyOptions.RepairLegacyUniqueDuplicates = !identity.applyOptions.RepairLegacyUniqueDuplicates
		}), want: "attempt apply options changed from their provenance"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			snapshots, err := captureApplyAllSourceRevisionAdvanceSnapshots(
				context.Background(), test.attempt,
			)
			if err == nil || snapshots.identity != nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("invalid attempt capture = %#v, %v, want pre-read rejection containing %q", snapshots, err, test.want)
			}
		})
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("invalid attempt reached snapshot SQL: %v", err)
	}
}

func testApplyAllSourceRevisionAdvanceSnapshotAttempt(
	t *testing.T,
	db *sql.DB,
	mock sqlmock.Sqlmock,
) (applyAllSourceRevisionAdvanceAttempt, *packs.SourceBackedPack) {
	t.Helper()
	advance := testApplyAllSourceRevisionAdvance("alpha", 3)
	target, _ := testApplyAllSourceRevisionAdvanceObjectTarget(
		t, advance.Name, advance.ToRevision, "SnapshotObject",
	)
	currentTarget := testApplyAllSourceRevisionAdvanceBeginTarget(
		t, advance, packs.AppPack, advance.FromRevision,
	)
	current := testApplyAllSourceRevisionAdvanceFrozenCurrentTargetProof(
		t, advance, currentTarget, installedPackStatusActive, advance.Kind,
		testCurrentTargetV1Hash, testCurrentTargetV1Hash,
	)
	atom, err := packs.FreezeSourceBackedExecutionAtom(target)
	if err != nil {
		t.Fatalf("freeze snapshot fixture atom: %v", err)
	}
	admission := testApplyAllSourceRevisionAdvanceExecutionAdmissionFromEntries(
		t, db, []applyAllSourceRevisionAdvanceExecutionProofEntry{{
			name: advance.Name, current: current.snapshot.entries[0],
			currentTarget: currentTarget, installedCompatibility: target, atom: atom,
		}},
	)
	oldRow := admission.identity.proof.entries[0].current.row
	receipt := testApplyAllSourceRevisionAdvanceBeginReceipt(
		t, context.Background(), db, mock, admission, 0, packs.ApplyOptions{},
	)
	expectApplyAllSourceRevisionAdvanceExactIncarnationBegin(mock, oldRow, packs.AppPack, false)
	mock.ExpectExec("UPDATE _System_InstalledPack p").WillReturnResult(sqlmock.NewResult(0, 1))
	expectApplyAllSourceRevisionAdvanceExactIncarnationCommit(mock)
	attempt, err := beginApplyAllSourceRevisionAdvanceAttempt(
		context.Background(), receipt,
	)
	if err != nil {
		t.Fatalf("begin snapshot fixture attempt: %v", err)
	}
	return attempt, target
}

func expectApplyAllSourceRevisionAdvanceSnapshotReads(
	mock sqlmock.Sqlmock,
	target *packs.SourceBackedPack,
	failStage string,
) {
	objectID := domainschema.GenerateObjectID("SnapshotObject")
	child := mock.ExpectQuery("SELECT .*ChildCloneRulesCustomized.*FROM `_System_Object`").WithArgs(objectID)
	if failStage == "child" {
		child.WillReturnError(errors.New("child snapshot failed"))
		return
	}
	child.WillReturnRows(sqlmock.NewRows([]string{
		"Id", "ChildCloneRulesCustomized", "ChildCloneRulesPackName", "ChildCloneRulesPackBusinessKey", "ChildCloneRulesPackManageability", "ChildCloneRulesPackValue",
	}))
	exclusive := mock.ExpectQuery("SELECT .*ExclusiveSiblingFieldsCustomized.*FROM `_System_Object`").WithArgs(objectID)
	if failStage == "exclusive" {
		exclusive.WillReturnError(errors.New("exclusive snapshot failed"))
		return
	}
	exclusive.WillReturnRows(sqlmock.NewRows([]string{
		"Id", "ExclusiveSiblingFieldsCustomized", "ExclusiveSiblingFieldsPackName", "ExclusiveSiblingFieldsPackBusinessKey", "ExclusiveSiblingFieldsPackManageability", "ExclusiveSiblingFieldsPackValue",
	}))
	connected := mock.ExpectQuery("SELECT Id, InstalledPackName, ConnectedAppId, PayloadHash").
		WithArgs(target.Name())
	if failStage == "connected" {
		connected.WillReturnError(errors.New("connected snapshot failed"))
		return
	}
	connected.WillReturnRows(sqlmock.NewRows([]string{
		"Id", "InstalledPackName", "ConnectedAppId", "PayloadHash", "IsDeleted",
	}))
}

func testApplyAllSourceRevisionAdvanceSnapshotMutation(
	snapshots applyAllSourceRevisionAdvanceSnapshots,
	mutate func(*applyAllSourceRevisionAdvanceSnapshotsIdentity),
) applyAllSourceRevisionAdvanceSnapshots {
	identity := *snapshots.identity
	provenance := *snapshots.identity.provenance
	identity.provenance = &provenance
	mutate(&identity)
	return applyAllSourceRevisionAdvanceSnapshots{identity: &identity, witness: snapshots.witness}
}
