package activity

import (
	"bytes"
	"context"
	"fmt"
	"path/filepath"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
	contentservice "github.com/tidbcloud/AgentTeam/internal/service/content"
	contentstore "github.com/tidbcloud/AgentTeam/internal/store/content"
	activity "github.com/tidbcloud/AgentTeam/internal/store/resource/activity"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func TestContentVersionOutboxProjectsIdempotentNativeActivityWithoutAGS(t *testing.T) {
	cases := []struct {
		name, domain, bindingKey, actor, path, assetType, assetID string
	}{
		{name: "automation", domain: "automation", bindingKey: "team/repo", actor: "automation:eventer", path: ".agentteam/build.yml", assetType: activity.AssetTypeAutomation, assetID: contentActivityAssetID("automation", "team/repo")},
		{name: "KB source", domain: "kb_source", bindingKey: "src_1", actor: "kb_source:indexer", path: "source.md", assetType: activity.AssetTypeKB, assetID: "src_1"},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			ctx := context.Background()
			dsn := "file:" + filepath.Join(t.TempDir(), "content-version-activity.db") + "?mode=memory&cache=shared"
			db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
			require.NoError(t, err)
			sqlDB, err := db.DB()
			require.NoError(t, err)
			t.Cleanup(func() { require.NoError(t, sqlDB.Close()) })
			require.NoError(t, db.AutoMigrate(
				&contentstore.VersionedContent{}, &contentstore.ContentVersion{}, &contentstore.VersionPath{},
				&contentstore.VersionPin{}, &contentstore.Outbox{}, &contentstore.StagedPayloadGC{},
				&activity.ResourceActivityEvent{}, &activity.ResourceActivityLatestCommit{},
			))
			tree, err := contentpayload.NewFilesystemTreeBackend(t.TempDir())
			require.NoError(t, err)
			t.Cleanup(func() { require.NoError(t, tree.Close()) })
			var ids atomic.Int64
			contentStore := contentstore.New(db)
			content, err := contentservice.NewTree(contentStore, tree, func(prefix string) (string, error) {
				return fmt.Sprintf("%s_%d", prefix, ids.Add(1)), nil
			})
			require.NoError(t, err)
			lineage, err := content.CreateContent(ctx, contentservice.CreateContentRequest{
				WorkspaceID: "wsp_1", Domain: tc.domain, BindingKey: tc.bindingKey, Model: contentstore.ModelTree, Actor: tc.actor,
			})
			require.NoError(t, err)
			committed, err := content.CommitVersion(ctx, contentservice.CommitVersionRequest{
				WorkspaceID: "wsp_1", ContentID: lineage.ID, ExpectedGeneration: lineage.Generation,
				Actor: tc.actor, Model: contentstore.ModelTree,
				Files:      []contentpayload.File{{Path: tc.path, Size: 6, Content: bytes.NewBufferString("review")}},
				TreeLimits: contentpayload.DefaultLimits,
			})
			require.NoError(t, err)

			consumer, err := NewContentVersionActivityConsumer(content, activity.New(db))
			require.NoError(t, err)
			worker, err := contentservice.NewOutboxWorker(contentStore, consumer, "worker-1", time.Minute)
			require.NoError(t, err)
			result, err := worker.RunOnce(ctx, 10)
			require.NoError(t, err)
			require.Equal(t, contentservice.OutboxWorkerResult{Leased: 1, Delivered: 1}, result)

			var outbox contentstore.Outbox
			require.NoError(t, db.Where("version_id = ?", committed.Version.ID).First(&outbox).Error)
			var projected activity.ResourceActivityEvent
			require.NoError(t, db.Where("source = ? AND source_event_id = ?", activity.SourceContentStore, outbox.ID).First(&projected).Error)
			require.Equal(t, tc.assetType, projected.AssetType)
			require.Equal(t, tc.assetID, projected.AssetID)
			require.Equal(t, committed.Version.ID, projected.TargetID)

			require.NoError(t, consumer.ConsumeVersionCommitted(ctx, contentservice.VersionCommittedEvent{
				ID: outbox.ID, WorkspaceID: outbox.WorkspaceID, ContentID: outbox.ContentID, VersionID: outbox.VersionID,
			}))
			var eventCount int64
			require.NoError(t, db.Model(&activity.ResourceActivityEvent{}).Where("source = ? AND source_event_id = ?", activity.SourceContentStore, outbox.ID).Count(&eventCount).Error)
			require.Equal(t, int64(1), eventCount)
		})
	}
}

func TestAutomationContentActivityAssetIDIsStableAndBounded(t *testing.T) {
	bindingKey := strings.Repeat("owner", 24) + "/" + strings.Repeat("repository", 20)
	first := contentActivityAssetID("automation", bindingKey)
	require.Len(t, first, 64)
	require.Equal(t, first, contentActivityAssetID("automation", strings.ToUpper(bindingKey)))
	require.NotEqual(t, first, contentActivityAssetID("automation", bindingKey+"-other"))
}
