package extension

import (
	"context"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
)

func TestListActiveAppRuntimeConnectionsReturnsRawIdentity(t *testing.T) {
	ctx := context.Background()
	db := startRollbackDB(t, ctx)
	now := time.Date(2026, 8, 11, 7, 0, 0, 0, time.UTC)
	fixture := seedExtensionStoreFixture(t, db, "runtime_conn", now)
	store := New(db)
	actors := serviceactor.New(db)

	definition, err := actors.UpsertDefinition(
		ctx,
		serviceactor.DefinitionUpsert{
			ID:               "app_runtime_conn",
			ServiceKind:      serviceactor.KindApp,
			InstallationMode: serviceactor.InstallationModeNamedMultiInstance,
			Slug:             "runtime-conn",
			Name:             "Runtime connection",
			LatestVersion:    "1.0.0",
			Status:           serviceactor.StatusActive,
			Now:              now,
		},
	)
	require.NoError(t, err)
	_, err = actors.UpsertInstallation(
		ctx,
		serviceactor.InstallationUpsert{
			ID:                  "appinst_runtime_conn",
			WorkspaceID:         fixture.WorkspaceID,
			ServiceDefinitionID: definition.ID,
			ServiceKind:         serviceactor.KindApp,
			InstanceKey:         "primary",
			Status:              serviceactor.StatusActive,
			InstalledVersion:    definition.LatestVersion,
			CreatedByMemberID:   fixture.HumanMemberID,
			Now:                 now,
		},
	)
	require.NoError(t, err)

	insertConnection := func(id, status string) {
		t.Helper()
		mustExec(t, db, `
INSERT INTO service_connections (
    id, workspace_id, service_definition_id, service_installation_id,
    service_kind, external_subject_ref, display_label,
    credentials, config, status, created_by_member_id, created_at, updated_at
) VALUES (
    $1, $2, $3, 'appinst_runtime_conn',
    'app', $4, '',
    $5, '{}', $6, $7, $8, $8
)`, id, fixture.WorkspaceID, definition.ID, "subject-"+id,
			[]byte(`{"_storage":"aes-gcm-v1","ciphertext":"opaque"}`),
			status, fixture.HumanMemberID, now)
	}
	insertConnection("appconn_raw", serviceactor.StatusActive)
	insertConnection("appconn_second", serviceactor.StatusActive)
	insertConnection("appconn_disabled", serviceactor.StatusDisabled)

	var connections []RuntimeConnection
	require.NoError(t, coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		var err error
		connections, err = store.InTx(tx).ListActiveAppRuntimeConnections(
			ctx,
			"appinst_runtime_conn",
		)
		return err
	}))

	require.Len(
		t,
		connections,
		2,
		"disabled connections must not enter runtime selection",
	)
	require.Equal(t, "appconn_raw", connections[0].ID)
	require.Equal(
		t,
		"subject-appconn_raw",
		connections[0].ExternalSubjectRef,
	)
	require.Equal(t, "appconn_second", connections[1].ID)
	for _, connection := range connections {
		require.Equal(t, fixture.WorkspaceID, connection.WorkspaceID)
		require.Equal(t, definition.ID, connection.ServiceDefinitionID)
		require.Equal(
			t,
			"appinst_runtime_conn",
			connection.ServiceInstallationID,
		)
		require.Equal(t, serviceactor.KindApp, connection.ServiceKind)
		require.NotEmpty(t, connection.ExternalSubjectRef)
		require.Equal(t, serviceactor.StatusActive, connection.Status)
		require.NotEmpty(t, connection.Credentials)
	}

	replacement := []byte(
		`{"_storage":"aes-gcm-v1","ciphertext":"new-opaque"}`,
	)
	require.NoError(t, coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		updated, err := store.InTx(tx).
			UpdateAppRuntimeConnectionCredentials(
				ctx,
				"appconn_second",
				connections[1].Credentials,
				replacement,
				now.Add(time.Minute),
			)
		require.True(t, updated)
		return err
	}))
	require.NoError(t, coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		updated, err := store.InTx(tx).
			UpdateAppRuntimeConnectionCredentials(
				ctx,
				"appconn_second",
				connections[1].Credentials,
				[]byte(
					`{"_storage":"aes-gcm-v1","ciphertext":"stale-overwrite"}`,
				),
				now.Add(2*time.Minute),
			)
		require.False(
			t,
			updated,
			"stale refresh must not overwrite newer durable credentials",
		)
		return err
	}))
	var durable []byte
	require.NoError(t, db.QueryRow(
		ctx,
		`SELECT credentials FROM service_connections WHERE id='appconn_second'`,
	).Scan(&durable))
	require.JSONEq(t, string(replacement), string(durable))
}

func TestListActiveAppRuntimeConnectionsReturnsEmptyForAbsentInstallation(
	t *testing.T,
) {
	ctx := context.Background()
	db := startRollbackDB(t, ctx)
	store := New(db)

	var connections []RuntimeConnection
	require.NoError(t, coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		var err error
		connections, err = store.InTx(tx).ListActiveAppRuntimeConnections(
			ctx,
			"appinst_absent",
		)
		return err
	}))
	require.Empty(t, connections)
}
