package automation

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"encoding/json"
	"fmt"
	"strings"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/AgentTeam/internal/contentpayload"
	contentstoreservice "github.com/tidbcloud/AgentTeam/internal/service/content"
	automationstore "github.com/tidbcloud/AgentTeam/internal/store/automation"
	contentstore "github.com/tidbcloud/AgentTeam/internal/store/content"
)

func TestDecodeAutomationChangedPathsUsesContentPublicationLimit(t *testing.T) {
	paths := make([]string, automationstore.MaxAutomationDefinitionsPerVersion+1)
	for index := range paths {
		paths[index] = fmt.Sprintf("generated/%05d.txt", index)
	}
	raw, err := json.Marshal(paths)
	require.NoError(t, err)
	decoded, err := decodeAutomationChangedPaths(string(raw))
	require.NoError(t, err)
	require.Equal(t, paths, decoded)

	paths = make([]string, contentpayload.DefaultLimits.MaxFiles+1)
	for index := range paths {
		paths[index] = fmt.Sprintf("generated/%05d.txt", index)
	}
	raw, err = json.Marshal(paths)
	require.NoError(t, err)
	_, err = decodeAutomationChangedPaths(string(raw))
	require.ErrorContains(t, err, "changed paths exceed publication limit")
}

func TestContentVersionAutomationConsumerCreatesOneExactRunOnReplay(t *testing.T) {
	db, err := openServiceTestSQLiteDB(t)
	require.NoError(t, err)
	migrateAutomationServiceTestSchema(t, db,
		&automationstore.AutomationContentPublication{}, &automationstore.AutomationContentDefinition{},
		&contentstore.VersionedContent{}, &contentstore.ContentVersion{}, &contentstore.VersionOperation{},
		&contentstore.VersionPath{}, &contentstore.VersionPin{}, &contentstore.Outbox{}, &contentstore.StagedPayloadGC{},
	)
	tree, err := contentpayload.NewFilesystemTreeBackend(t.TempDir())
	require.NoError(t, err)
	t.Cleanup(func() { require.NoError(t, tree.Close()) })
	var sequence atomic.Int64
	contentDB := contentstore.New(db)
	content, err := contentstoreservice.NewTree(contentDB, tree, func(prefix string) (string, error) {
		return fmt.Sprintf("%s_%d", prefix, sequence.Add(1)), nil
	})
	require.NoError(t, err)
	ctx := context.Background()
	lineage, err := content.CreateContent(ctx, contentstoreservice.CreateContentRequest{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "team/repo", Model: contentstore.ModelTree, Actor: "automation:eventer",
	})
	require.NoError(t, err)
	source := "name: Build\non: push\njobs:\n  build:\n    steps:\n      - run: echo ok\n"
	digest := sha256.Sum256([]byte(source))
	committed, err := content.CommitVersion(ctx, contentstoreservice.CommitVersionRequest{
		WorkspaceID: "wsp_1", ContentID: lineage.ID, ExpectedGeneration: lineage.Generation,
		Actor: "automation:eventer", Model: contentstore.ModelTree, TreeLimits: contentpayload.DefaultLimits,
		Files: []contentpayload.File{{Path: ".agentteam/build.yml", Size: int64(len(source)), Content: bytes.NewBufferString(source)}},
	})
	require.NoError(t, err)
	now := time.Date(2026, 8, 22, 0, 0, 0, 0, time.UTC)
	automations := automationstore.New(db)
	require.NoError(t, automations.RecordContentPublication(ctx, automationstore.AutomationContentPublication{
		WorkspaceID: "wsp_1", ContentID: lineage.ID, ContentVersionID: committed.Version.ID,
		RepositoryOwner: "team", RepositoryName: "repo", Ref: "main",
		ChangedPathsJSON: `[".agentteam/build.yml"]`, ActorID: AutomationEventerPrincipalID, CreatedAt: now,
	}, []automationstore.AutomationContentDefinition{{DefinitionPath: ".agentteam/build.yml", Digest: hex.EncodeToString(digest[:]), SizeBytes: int64(len(source))}}, "", automationstore.ContentPublicationRecordStrict))
	service := NewAutomationsService(automations, func() time.Time { return now }, nil)
	service.SetAuthorizer(AllowAutomationAuthorizer{})
	consumer, err := NewContentVersionAutomationConsumer(content, service)
	require.NoError(t, err)
	event := contentstoreservice.VersionCommittedEvent{ID: "outbox_1", WorkspaceID: "wsp_1", ContentID: lineage.ID, VersionID: committed.Version.ID}
	require.NoError(t, db.Model(&automationstore.AutomationContentDefinition{}).
		Where("workspace_id = ? AND content_id = ? AND content_version_id = ?", event.WorkspaceID, event.ContentID, event.VersionID).
		Update("digest", strings.Repeat("0", 64)).Error)
	require.ErrorContains(t, consumer.ConsumeVersionCommitted(ctx, event), "projection mismatch")
	require.NoError(t, db.Model(&automationstore.AutomationContentDefinition{}).
		Where("workspace_id = ? AND content_id = ? AND content_version_id = ?", event.WorkspaceID, event.ContentID, event.VersionID).
		Update("digest", hex.EncodeToString(digest[:])).Error)
	require.NoError(t, consumer.ConsumeVersionCommitted(ctx, event))
	require.NoError(t, consumer.ConsumeVersionCommitted(ctx, event))

	run, err := automations.GetRunByIdempotencyKey(ctx, "wsp_1", contentVersionAutomationIdempotencyKey(event.ID, ".agentteam/build.yml"))
	require.NoError(t, err)
	require.Equal(t, automationSourceKindContentVersion, run.SourceKind)
	require.Equal(t, lineage.ID, run.ContentID)
	require.Equal(t, committed.Version.ID, run.ContentVersionID)
	require.Equal(t, ".agentteam/build.yml", run.DefinitionPath)
	require.Empty(t, run.CommitSHA)
	var count int64
	require.NoError(t, db.Model(&automationstore.AutomationRun{}).Count(&count).Error)
	require.Equal(t, int64(1), count)
}
