package packs

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

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/kernel/pkg/formula"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

func TestRegisterRuntimeMetadataForInstallWithOverridesAndComponentKindRegistrationSetRehydratesActiveRegisteredPeer(t *testing.T) {
	fixture := newRuntimeMetadataRegistrationCarrierFixture(t)
	installing := &runtimeMetadataValidationPack{name: "installing-runtime-pack"}
	t.Cleanup(func() {
		if err := RegisterL0RuntimeMetadata(context.Background(), nil); err != nil {
			t.Errorf("restore L0 runtime metadata: %v", err)
		}
	})

	tests := []struct {
		name string
		call func(*sql.DB) error
	}{
		{
			name: "singular",
			call: func(db *sql.DB) error {
				return RegisterRuntimeMetadataForInstallWithOverridesAndComponentKindRegistrationSet(
					context.Background(), db, installing, nil, fixture.registrations,
				)
			},
		},
		{
			name: "plural",
			call: func(db *sql.DB) error {
				return RegisterRuntimeMetadataForInstallingPacksWithOverridesAndComponentKindRegistrationSet(
					context.Background(), db, []Pack{installing}, nil, fixture.registrations,
				)
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			db, mock := fixture.activePeerDB(t)
			if err := tt.call(db); err != nil {
				t.Fatalf("registration-aware runtime metadata carrier: %v", err)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

func TestActiveInstalledRuntimeSnapshotRegistersFrozenPeerReplacementAndRestoreWithoutReread(t *testing.T) {
	type frozenPack struct {
		pack        *SourceBackedPack
		source      string
		hash        string
		declaration persistedRuntimeDeclaration
	}
	buildPack := func(name, functionName string) frozenPack {
		manifest := validPackManifest(`[
			`+formulaFunctionBundleManifestComponent(strings.Replace(
			testFormulaFunctionBundleComponentPayload(), "PACK_PREFIX", functionName, 1,
		))+`
		]`, ``)
		manifest = strings.Replace(manifest, `"name": "jira-like-sample"`, `"name": "`+name+`"`, 1)
		source := writePackFixture(t, name, manifest)
		pack, err := ResolveInstalledSourceBackedPack(
			name, AppPack, string(PackSourceKindLocalPath), source,
		)
		if err != nil {
			t.Fatalf("resolve %s: %v", name, err)
		}
		hash, err := PackPayloadHash(pack)
		if err != nil {
			t.Fatalf("hash %s: %v", name, err)
		}
		declaration, err := BuildPersistedRuntimeDeclaration(pack)
		if err != nil {
			t.Fatalf("build persisted declaration for %s: %v", name, err)
		}
		return frozenPack{pack: pack, source: source, hash: hash, declaration: declaration}
	}

	peer := buildPack("frozen-peer", "FROZEN_PEER")
	oldRelease := buildPack("release-pack", "OLD_RELEASE")
	candidate := buildPack("candidate-pack", "CANDIDATE")
	replacement := buildPack("release-pack", "NEW_RELEASE")
	t.Cleanup(func() {
		if err := RegisterL0RuntimeMetadata(context.Background(), nil); err != nil {
			t.Errorf("restore L0 runtime metadata: %v", err)
		}
	})

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()
	expectInstalledPackRuntimeDeclarationColumnsWithStartupShallowAndFormulaBundles(mock, true, true, true)
	rows := sqlmock.NewRows([]string{
		"Name", "Category", "AppliedPayloadHash", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision",
		"RuntimeObjectSpecDeclarations", "RuntimeRelatedListRules", "RuntimeSearchScopeOrder", "RuntimeSearchAllDefaultFields", "RuntimeFormulaFunctionBundles", "RuntimeStartupShallowDeclarations",
	})
	for _, active := range []frozenPack{peer, oldRelease} {
		rows.AddRow(
			active.pack.Name(), string(AppPack), active.hash, SupportedManifestFormatVersion,
			string(PackSourceKindLocalPath), active.source, nil,
			active.declaration.ObjectPayloadsJSON(), active.declaration.RelatedListRulesJSON(),
			active.declaration.SearchScopeOrderJSON(), active.declaration.SearchAllDefaultFieldsJSON(),
			active.declaration.FormulaFunctionBundlesJSON(), active.declaration.StartupShallowDeclaredRowsJSON(),
		)
	}
	mock.ExpectQuery("SELECT Name, Category, AppliedPayloadHash, ManifestFormatVersion, SourceKind, SourceURI, SourceRevision,\\s+RuntimeObjectSpecDeclarations, RuntimeRelatedListRules, RuntimeSearchScopeOrder, RuntimeSearchAllDefaultFields, RuntimeFormulaFunctionBundles, RuntimeStartupShallowDeclarations\\s+FROM _System_InstalledPack").
		WillReturnRows(rows)
	snapshot, err := ReadActiveInstalledRuntimeSnapshot(context.Background(), db)
	if err != nil {
		t.Fatalf("read active runtime snapshot: %v", err)
	}
	allFunctions := []string{"FROZEN_PEER", "OLD_RELEASE", "CANDIDATE", "NEW_RELEASE"}
	assertFunctions := func(want ...string) {
		t.Helper()
		wantSet := make(map[string]bool, len(want))
		for _, name := range want {
			wantSet[name] = true
		}
		registry, err := formula.ActiveRuntimeExecutableFunctionRegistry()
		if err != nil {
			t.Fatalf("active runtime function registry: %v", err)
		}
		for _, name := range allFunctions {
			_, present := registry.LookupFunctionDefinition(name)
			if present != wantSet[name] {
				t.Fatalf("runtime function %s present = %t, want %t", name, present, wantSet[name])
			}
		}
	}

	if err := snapshot.RegisterRuntimeMetadataForInstallingPacks(
		context.Background(), []Pack{candidate.pack}, ComponentKindRegistrationSet{},
	); err != nil {
		t.Fatalf("register frozen candidate runtime metadata: %v", err)
	}
	assertFunctions("FROZEN_PEER", "OLD_RELEASE", "CANDIDATE")
	if err := snapshot.RegisterRuntimeMetadataForInstallingPacks(
		context.Background(), []Pack{replacement.pack}, ComponentKindRegistrationSet{},
	); err != nil {
		t.Fatalf("register same-name replacement runtime metadata: %v", err)
	}
	assertFunctions("FROZEN_PEER", "NEW_RELEASE")
	if err := snapshot.RegisterRuntimeMetadataForInstallingPacks(
		context.Background(), nil, ComponentKindRegistrationSet{},
	); err != nil {
		t.Fatalf("restore frozen active runtime metadata: %v", err)
	}
	assertFunctions("FROZEN_PEER", "OLD_RELEASE")
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("snapshot-backed registration re-read installed rows: %v", err)
	}
}

func TestActiveInstalledRuntimeSnapshotRegistrationRejectsUnmintedBeforeSQL(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	for _, snapshot := range []*ActiveInstalledRuntimeSnapshot{nil, {}, {db: db}} {
		err := snapshot.RegisterRuntimeMetadataForInstallingPacks(
			context.Background(), nil, ComponentKindRegistrationSet{},
		)
		if err == nil || !strings.Contains(err.Error(), "not minted") {
			t.Fatalf("unminted snapshot registration error = %v, want fail closed", err)
		}
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("unminted snapshot registration reached SQL: %v", err)
	}
}

func TestActiveInstalledRuntimeSnapshotSeparatesCandidateAndFrozenReleaseAuthorities(t *testing.T) {
	const uri = "urn:nexuscrm:release-pack:shared"
	const candidateRevision = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	const frozenRevision = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
	snapshot := &ActiveInstalledRuntimeSnapshot{
		db: &sql.DB{},
		rows: []activeInstalledPackRow{{
			name:           "frozen-peer",
			sourceKind:     sql.NullString{String: string(PackSourceKindReleaseRepo), Valid: true},
			sourceURI:      sql.NullString{String: uri, Valid: true},
			sourceRevision: sql.NullString{String: frozenRevision, Valid: true},
		}},
	}
	ctx := WithOperationPackSourceOptions(context.Background(), PackSourceOptions{
		ReleaseRepoSources: map[string]ReleaseRepoSource{
			uri: {Root: "/exact/repository/pack", Revision: candidateRevision},
		},
	})
	gotCtx, err := snapshot.withFrozenReleaseRepoAuthorities(ctx)
	if err != nil {
		t.Fatalf("freeze active release authorities: %v", err)
	}
	options, ok := OperationPackSourceOptions(gotCtx)
	if !ok {
		t.Fatal("frozen active release authority context is missing")
	}
	if got := options.ReleaseRepoSources[uri].Revision; got != candidateRevision {
		t.Fatalf("candidate revision = %q, want %q", got, candidateRevision)
	}
	key := releaseRepoStoredRevisionAuthorityKey(uri, frozenRevision)
	if got := options.ReleaseRepoStoredRevisionSources[key]; got.Root != "/exact/repository/pack" || got.Revision != frozenRevision {
		t.Fatalf("frozen authority = %#v, want exact before revision", got)
	}
}

func TestActiveInstalledRuntimeSnapshotPackListLoadsFrozenReleasePeer(t *testing.T) {
	const uri = "urn:nexuscrm:release-pack:shared-runtime"
	repoRoot := t.TempDir()
	packRoot := repoRoot + "/packs/runtime"
	writeManifest(t, packRoot, validPackManifest(`[
		{
			"kind": "object_spec",
			"businessKey": "RuntimeProbe",
			"manageability": "locked",
			"path": "objects/runtime_probe.json"
		}
	]`, `"dependencies": [],`))
	writeFile(t, packRoot, "objects/runtime_probe.json", testObjectSpecPayloadWithFields(
		"RuntimeProbe",
		`{"apiName":"Name","label":"Name","type":"string","required":true,"isNameField":true}`,
	))
	initGitTestRepo(t, repoRoot)
	frozenRevision := gitTestCommit(t, repoRoot, "frozen runtime pack")

	frozenOptions := testPackSourceOptions(t)
	frozenOptions.ReleaseRepoSources = map[string]ReleaseRepoSource{
		uri: {Root: packRoot, Revision: frozenRevision},
	}
	report := ValidateInstallablePackSourceWithOptions(PackSourceKindReleaseRepo+":"+uri, frozenOptions)
	if report.HasErrors() || report.Pack == nil {
		t.Fatalf("resolve frozen runtime pack: %s", report.Error())
	}
	payloadHash, err := PackPayloadHash(report.Pack)
	if err != nil {
		t.Fatal(err)
	}
	writeFile(t, repoRoot, "candidate-marker.txt", "candidate")
	candidateRevision := gitTestCommit(t, repoRoot, "candidate checkout")

	snapshot := &ActiveInstalledRuntimeSnapshot{
		db: &sql.DB{},
		rows: []activeInstalledPackRow{{
			name:                  report.Pack.Name(),
			category:              string(report.Pack.Category()),
			appliedPayloadHash:    sql.NullString{String: payloadHash, Valid: true},
			manifestFormatVersion: sql.NullString{String: SupportedManifestFormatVersion, Valid: true},
			sourceKind:            sql.NullString{String: string(PackSourceKindReleaseRepo), Valid: true},
			sourceURI:             sql.NullString{String: uri, Valid: true},
			sourceRevision:        sql.NullString{String: frozenRevision, Valid: true},
		}},
	}
	ctx := WithOperationPackSourceOptions(context.Background(), PackSourceOptions{
		ReleaseRepoSources: map[string]ReleaseRepoSource{
			uri: {Root: packRoot, Revision: candidateRevision},
		},
	})
	packList, err := snapshot.runtimeDeclarationContextForInstall(ctx, nil, nil, nil)
	if err != nil {
		t.Fatalf("load frozen release peer beside candidate revision: %v", err)
	}
	if len(packList) != 1 || packList[0].Name() != report.Pack.Name() {
		t.Fatalf("runtime pack list = %#v, want frozen peer %q", packList, report.Pack.Name())
	}
}

func TestRuntimeDeclarationOverrideDoesNotReopenCandidateOnlyReleaseAuthority(t *testing.T) {
	const uri = "urn:nexuscrm:release-pack:shared"
	const candidateRevision = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
	const frozenRevision = "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
	override := &runtimeMetadataValidationPack{name: "frozen-peer"}
	ctx := WithOperationPackSourceOptions(context.Background(), PackSourceOptions{
		ReleaseRepoSources: map[string]ReleaseRepoSource{
			uri: {Root: "/candidate-only/root", Revision: candidateRevision},
		},
	})
	packList, err := runtimeDeclarationContextForInstallFromActiveRowsWithComponentKindRegistrationSet(
		ctx,
		&sql.DB{},
		nil,
		map[string]Pack{},
		map[string]Pack{"frozen-peer": override},
		nil,
		[]activeInstalledPackRow{{
			name:           "frozen-peer",
			sourceKind:     sql.NullString{String: string(PackSourceKindReleaseRepo), Valid: true},
			sourceURI:      sql.NullString{String: uri, Valid: true},
			sourceRevision: sql.NullString{String: frozenRevision, Valid: true},
		}},
	)
	if err != nil {
		t.Fatalf("compose runtime declaration with proof-owned frozen override: %v", err)
	}
	if len(packList) != 1 || packList[0] != override {
		t.Fatalf("runtime declaration packs = %#v, want exact frozen override", packList)
	}
}

func TestRegisterRuntimeMetadataForInstallWithOverridesLegacyEntryPointsRejectActiveRegisteredPeer(t *testing.T) {
	fixture := newRuntimeMetadataRegistrationCarrierFixture(t)
	installing := &runtimeMetadataValidationPack{name: "installing-runtime-pack"}

	tests := []struct {
		name string
		call func(*sql.DB) error
	}{
		{
			name: "singular",
			call: func(db *sql.DB) error {
				return RegisterRuntimeMetadataForInstallWithOverrides(context.Background(), db, installing, nil)
			},
		},
		{
			name: "plural",
			call: func(db *sql.DB) error {
				return RegisterRuntimeMetadataForInstallingPacksWithOverrides(context.Background(), db, []Pack{installing}, nil)
			},
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			db, mock := fixture.activePeerDB(t)
			err := tt.call(db)
			if err == nil || !strings.Contains(err.Error(), string(fixture.managedKind)) {
				t.Fatalf("legacy runtime metadata carrier error = %v, want unregistered kind %q", err, fixture.managedKind)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatalf("sql expectations: %v", err)
			}
		})
	}
}

type runtimeMetadataRegistrationCarrierFixture struct {
	managedKind   cp.ComponentKind
	registrations ComponentKindRegistrationSet
	activePack    Pack
	activeHash    string
	activeSource  string
	declaration   persistedRuntimeDeclaration
}

func newRuntimeMetadataRegistrationCarrierFixture(t *testing.T) runtimeMetadataRegistrationCarrierFixture {
	t.Helper()

	managedKind := cp.ComponentKind("registered_runtime_context_test")
	registration := testComponentKindRegistration(managedKind, "RegisteredRuntimeContext")
	registration.BuildHandler = func(ctx cp.ComponentBuildContext, _ cp.ComponentBuildRequest) error {
		return ctx.AddManagedComponent(cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{"rows":[]}`)})
	}
	registrations, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{registration})
	if err != nil {
		t.Fatalf("registration set: %v", err)
	}
	manifest := strings.Replace(validPackManifest(`[
		{
			"kind": "registered_runtime_context_test",
			"businessKey": "Registered/Peer",
			"manageability": "locked",
			"payload": {}
		}
	]`, ``), `"name": "jira-like-sample"`, `"name": "registered-runtime-peer"`, 1)
	activeSource := writePackFixture(t, "registered-runtime-peer", manifest)
	report := ValidateInstallablePackSourceWithComponentKindRegistrationSet(
		"local_path:"+activeSource, testPackSourceOptions(t), registrations,
	)
	if report.HasErrors() || report.Pack == nil {
		t.Fatalf("active peer source diagnostics: %s", report.Error())
	}
	activeHash, err := PackPayloadHash(report.Pack)
	if err != nil {
		t.Fatalf("active peer payload hash: %v", err)
	}
	declaration, err := BuildPersistedRuntimeDeclaration(report.Pack)
	if err != nil {
		t.Fatalf("active peer runtime declaration: %v", err)
	}
	return runtimeMetadataRegistrationCarrierFixture{
		managedKind:   managedKind,
		registrations: registrations,
		activePack:    report.Pack,
		activeHash:    activeHash,
		activeSource:  activeSource,
		declaration:   declaration,
	}
}

func (f runtimeMetadataRegistrationCarrierFixture) activePeerDB(t *testing.T) (*sql.DB, sqlmock.Sqlmock) {
	t.Helper()

	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	t.Cleanup(func() { _ = db.Close() })
	expectInstalledPackRuntimeDeclarationColumnsWithStartupShallowAndFormulaBundles(mock, true, true, true)
	mock.ExpectQuery("SELECT Name, Category, AppliedPayloadHash, ManifestFormatVersion, SourceKind, SourceURI, SourceRevision,\\s+RuntimeObjectSpecDeclarations, RuntimeRelatedListRules, RuntimeSearchScopeOrder").
		WillReturnRows(sqlmock.NewRows([]string{
			"Name", "Category", "AppliedPayloadHash", "ManifestFormatVersion", "SourceKind", "SourceURI", "SourceRevision",
			"RuntimeObjectSpecDeclarations", "RuntimeRelatedListRules", "RuntimeSearchScopeOrder", "RuntimeSearchAllDefaultFields", "RuntimeFormulaFunctionBundles", "RuntimeStartupShallowDeclarations",
		}).AddRow(
			f.activePack.Name(), string(AppPack), f.activeHash, SupportedManifestFormatVersion,
			string(PackSourceKindLocalPath), f.activeSource, nil,
			f.declaration.ObjectPayloadsJSON(), f.declaration.RelatedListRulesJSON(), f.declaration.SearchScopeOrderJSON(),
			f.declaration.SearchAllDefaultFieldsJSON(), f.declaration.FormulaFunctionBundlesJSON(), f.declaration.StartupShallowDeclaredRowsJSON(),
		))
	return db, mock
}
