package automation

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

	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

func TestCurrentContentRepositoryProjectionIgnoresHistoricalVolumeAndOrder(t *testing.T) {
	store := newContentPublicationTestStore(t)
	base := time.Date(2026, 8, 22, 0, 0, 0, 0, time.UTC)
	previous := ""
	for version := 1; version <= 200; version++ {
		versionID := fmt.Sprintf("ctv_%03d", version)
		require.NoError(t, store.RecordContentPublication(context.Background(), AutomationContentPublication{
			WorkspaceID: "wsp_1", ContentID: "ctn_1", ContentVersionID: versionID,
			RepositoryOwner: "team", RepositoryName: "repo", Ref: "main", ChangedPathsJSON: "[]",
			ActorID: "usr_1", CreatedAt: base.Add(time.Duration(version) * time.Second),
		}, nil, previous, ContentPublicationRecordStrict))
		previous = versionID
	}
	rows, err := store.ListLatestContentPublicationRepositories(context.Background(), "wsp_1", 0, 10)
	require.NoError(t, err)
	require.Len(t, rows, 1)
	require.Equal(t, "ctv_200", rows[0].ContentVersionID)

	err = store.RecordContentPublication(context.Background(), AutomationContentPublication{
		WorkspaceID: "wsp_1", ContentID: "ctn_1", ContentVersionID: "ctv_delayed",
		RepositoryOwner: "team", RepositoryName: "repo", Ref: "old", ChangedPathsJSON: "[]",
		ActorID: "usr_1", CreatedAt: base,
	}, nil, "", ContentPublicationRecordStrict)
	require.ErrorContains(t, err, "current content repository conflicts")
	rows, err = store.ListLatestContentPublicationRepositories(context.Background(), "wsp_1", 0, 10)
	require.NoError(t, err)
	require.Len(t, rows, 1)
	require.Equal(t, "ctv_200", rows[0].ContentVersionID, "out-of-order publication must not regress the current projection")

	_, err = store.ListLatestContentPublicationRepositories(context.Background(), "wsp_1", MaxAutomationRepositoriesPerWorkspace, 1)
	require.ErrorContains(t, err, "page is invalid")
}

func TestContentPublicationV1V2V3ConcurrentReplayV2DoesNotRegressCurrent(t *testing.T) {
	store := newContentPublicationTestStore(t)
	base := time.Date(2026, 8, 22, 0, 0, 0, 0, time.UTC)
	previous := ""
	for version := 1; version <= 3; version++ {
		versionID := fmt.Sprintf("ctv_v%d", version)
		require.NoError(t, store.RecordContentPublication(t.Context(), AutomationContentPublication{
			WorkspaceID: "wsp_1", ContentID: "ctn_1", ContentVersionID: versionID,
			RepositoryOwner: "team", RepositoryName: "repo", Ref: "main", ChangedPathsJSON: "[]",
			ActorID: "usr_1", CreatedAt: base.Add(time.Duration(version) * time.Second),
		}, nil, previous, ContentPublicationRecordStrict))
		previous = versionID
	}

	const retries = 8
	errors := make(chan error, retries)
	var retriesDone sync.WaitGroup
	for range retries {
		retriesDone.Add(1)
		go func() {
			defer retriesDone.Done()
			errors <- store.RecordContentPublication(context.Background(), AutomationContentPublication{
				WorkspaceID: "wsp_1", ContentID: "ctn_1", ContentVersionID: "ctv_v2",
				RepositoryOwner: "team", RepositoryName: "repo", Ref: "main", ChangedPathsJSON: "[]",
				ActorID: "usr_1", CreatedAt: base.Add(2 * time.Second),
			}, nil, "ctv_v1", ContentPublicationRecordStrict)
		}()
	}
	retriesDone.Wait()
	close(errors)
	for err := range errors {
		require.NoError(t, err)
	}
	current, err := store.GetCurrentContentRepository(t.Context(), "wsp_1", "team", "repo")
	require.NoError(t, err)
	require.Equal(t, "ctv_v3", current.ContentVersionID)
}

func TestRecoverContentPublicationBackfillsMissingV2AfterV3WithoutRegressingCurrent(t *testing.T) {
	store := newContentPublicationTestStore(t)
	base := time.Date(2026, 8, 22, 0, 0, 0, 0, time.UTC)
	publication := func(versionID string, sequence int) AutomationContentPublication {
		return AutomationContentPublication{
			WorkspaceID: "wsp_1", ContentID: "ctn_1", ContentVersionID: versionID,
			RepositoryOwner: "team", RepositoryName: "repo", Ref: "main",
			ChangedPathsJSON: `[".agentteam/build.yml"]`, ActorID: "usr_1",
			CreatedAt: base.Add(time.Duration(sequence) * time.Second),
		}
	}
	recoveredDefinitions := func() []AutomationContentDefinition {
		return []AutomationContentDefinition{{
			DefinitionPath: ".agentteam/build.yml", Digest: strings.Repeat("2", 64), SizeBytes: 42,
		}}
	}
	require.NoError(t, store.RecordContentPublication(t.Context(), publication("ctv_v1", 1), nil, "", ContentPublicationRecordStrict))
	require.NoError(t, store.RecordContentPublication(t.Context(), publication("ctv_v3", 3), nil, "ctv_v1", ContentPublicationRecordStrict))

	err := store.RecordContentPublication(t.Context(), publication("ctv_v2", 2), recoveredDefinitions(), "ctv_v1", ContentPublicationRecordStrict)
	require.ErrorContains(t, err, "current content repository conflicts", "ordinary out-of-order writes must remain fail-closed")

	const retries = 8
	errors := make(chan error, retries)
	var retriesDone sync.WaitGroup
	for range retries {
		retriesDone.Add(1)
		go func() {
			defer retriesDone.Done()
			errors <- store.RecordContentPublication(context.Background(), publication("ctv_v2", 2), recoveredDefinitions(), "ctv_v1", ContentPublicationRecordRecovery)
		}()
	}
	retriesDone.Wait()
	close(errors)
	for err := range errors {
		require.NoError(t, err)
	}

	current, err := store.GetCurrentContentRepository(t.Context(), "wsp_1", "team", "repo")
	require.NoError(t, err)
	require.Equal(t, "ctv_v3", current.ContentVersionID)
	recovered, err := store.GetContentPublication(t.Context(), "wsp_1", "ctn_1", "ctv_v2")
	require.NoError(t, err)
	require.Equal(t, "ctv_v2", recovered.ContentVersionID)
	definitions, err := store.ListContentDefinitions(t.Context(), "wsp_1", "ctn_1", "ctv_v2", MaxAutomationDefinitionsPerVersion)
	require.NoError(t, err)
	require.Equal(t, recoveredDefinitions()[0].DefinitionPath, definitions[0].DefinitionPath)
	require.Equal(t, recoveredDefinitions()[0].Digest, definitions[0].Digest)
}

func newContentPublicationTestStore(t *testing.T) *Store {
	t.Helper()
	dsn := "file:" + filepath.Join(t.TempDir(), "content-publications.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)
	sqlDB.SetMaxOpenConns(1)
	t.Cleanup(func() { require.NoError(t, sqlDB.Close()) })
	require.NoError(t, db.AutoMigrate(&AutomationContentPublication{}, &AutomationContentRepository{}, &AutomationContentDefinition{}))
	return New(db)
}
