package content

import (
	"context"
	"errors"
	"fmt"
	"testing"
	"time"

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

func newAuthorityStore(t *testing.T) (*Store, *gorm.DB) {
	t.Helper()
	db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &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(&Authority{}))
	return New(db), db
}

func TestAuthorityMissingRowDefaultsLegacyWithoutWrite(t *testing.T) {
	store, db := newAuthorityStore(t)
	authority, found, err := store.GetAuthority(context.Background(), "wsp_1", "skill", "skl_1")
	require.NoError(t, err)
	require.False(t, found)
	require.Equal(t, AuthorityStateLegacyActive, authority.State)
	var count int64
	require.NoError(t, db.Model(&Authority{}).Count(&count).Error)
	require.Zero(t, count)
}

func TestListAuthoritiesForBindingsIsExactBoundedAndDoesNotMaterializeMissingRows(t *testing.T) {
	store, db := newAuthorityStore(t)
	ctx := context.Background()
	_, err := store.ActivateNativeAuthority(ctx, ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "skill", BindingKey: "skl_1",
		Checkpoint: "import:1", Actor: "usr_1", Now: time.Now().UTC(),
	})
	require.NoError(t, err)

	rows, err := store.ListAuthoritiesForBindings(ctx, "wsp_1", "skill", []string{"skl_1", "skl_missing", "skl_1"})
	require.NoError(t, err)
	require.Len(t, rows, 1)
	require.Equal(t, AuthorityStateNativeActive, rows["skl_1"].State)
	var count int64
	require.NoError(t, db.Model(&Authority{}).Count(&count).Error)
	require.EqualValues(t, 1, count)

	_, err = store.ListAuthoritiesForBindings(ctx, "wsp_1", "skill", []string{AuthorityDomainDefaultBinding})
	require.Error(t, err)
	tooMany := make([]string, MaxAuthorityBindingBatch+1)
	for index := range tooMany {
		tooMany[index] = fmt.Sprintf("skl_%d", index)
	}
	_, err = store.ListAuthoritiesForBindings(ctx, "wsp_1", "skill", tooMany)
	require.ErrorContains(t, err, "exceeds")
}

func TestActivateNativeAuthorityIsOneWayAndGenerationCASProtected(t *testing.T) {
	store, _ := newAuthorityStore(t)
	ctx := context.Background()
	now := time.Date(2026, 8, 21, 14, 0, 0, 0, time.UTC)
	params := ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "skill", BindingKey: "skl_1",
		Checkpoint: "import:1", Actor: "usr_1", Now: now,
	}
	native, err := store.ActivateNativeAuthority(ctx, params)
	require.NoError(t, err)
	require.Equal(t, AuthorityStateNativeActive, native.State)
	require.Equal(t, uint64(1), native.Generation)

	// A second activation cannot turn the binding into another state or reuse
	// the implicit-legacy generation.
	_, err = store.ActivateNativeAuthority(ctx, params)
	require.ErrorIs(t, err, ErrAuthorityConflict)
	params.ExpectedGeneration = native.Generation
	_, err = store.ActivateNativeAuthority(ctx, params)
	require.ErrorIs(t, err, ErrAuthorityConflict)

	exact, found, err := store.GetAuthority(ctx, "wsp_1", "skill", "skl_1")
	require.NoError(t, err)
	require.True(t, found)
	require.Equal(t, native, exact)
}

func TestActivateNativeAuthorityRejectsWildcardBinding(t *testing.T) {
	store, _ := newAuthorityStore(t)
	_, err := store.ActivateNativeAuthority(context.Background(), ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "skill", BindingKey: AuthorityDomainDefaultBinding,
		Checkpoint: "import:final", Actor: "operator", Now: time.Now().UTC(),
	})
	require.ErrorContains(t, err, "requires an exact binding")
}

func TestActivateNativeAuthorityAcceptsExplicitLegacyGeneration(t *testing.T) {
	store, db := newAuthorityStore(t)
	now := time.Now().UTC()
	require.NoError(t, db.Create(&Authority{
		ID: "cta_legacy", WorkspaceID: "wsp_1", Domain: "role", BindingKey: "rol_1",
		State: AuthorityStateLegacyActive, Generation: 4, UpdatedBy: "legacy", CreatedAt: now, UpdatedAt: now,
	}).Error)

	native, err := store.ActivateNativeAuthority(context.Background(), ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "role", BindingKey: "rol_1", ExpectedGeneration: 4,
		Checkpoint: "import:final", Actor: "operator", Now: now.Add(time.Minute),
	})
	require.NoError(t, err)
	require.Equal(t, AuthorityStateNativeActive, native.State)
	require.Equal(t, uint64(5), native.Generation)
	require.Equal(t, "import:final", native.Checkpoint)
}

func TestActivateNativeAuthorityInitialCASHasOneWinner(t *testing.T) {
	store, _ := newAuthorityStore(t)
	params := ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "role", BindingKey: "rol_1",
		Checkpoint: "import:1", Actor: "usr_1", Now: time.Now().UTC(),
	}
	_, firstErr := store.ActivateNativeAuthority(context.Background(), params)
	_, secondErr := store.ActivateNativeAuthority(context.Background(), params)
	require.NoError(t, firstErr)
	require.True(t, errors.Is(secondErr, ErrAuthorityConflict))
}

func TestLegacyAuthorityFenceSerializesMigrationAndActivation(t *testing.T) {
	store, _ := newAuthorityStore(t)
	ctx := context.Background()
	now := time.Date(2026, 8, 23, 4, 0, 0, 0, time.UTC)
	first, err := store.AcquireLegacyAuthorityFence(ctx, AcquireLegacyAuthorityFenceParams{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "team/repo",
		Token: "migration-1", Actor: "operator", Now: now, ExpiresAt: now.Add(time.Minute),
	})
	require.NoError(t, err)
	_, err = store.AcquireLegacyAuthorityFence(ctx, AcquireLegacyAuthorityFenceParams{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "team/repo",
		Token: "activation-1", Actor: "operator", Now: now, ExpiresAt: now.Add(time.Minute),
	})
	require.ErrorIs(t, err, ErrAuthorityConflict)

	secondNow := now.Add(2 * time.Minute)
	second, err := store.AcquireLegacyAuthorityFence(ctx, AcquireLegacyAuthorityFenceParams{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "team/repo",
		Token: "activation-1", Actor: "operator", Now: secondNow, ExpiresAt: secondNow.Add(time.Minute),
	})
	require.NoError(t, err)
	_, err = store.ActivateNativeAuthority(ctx, ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "team/repo",
		ExpectedGeneration: 0, ExpectedCheckpoint: first.Checkpoint,
		Checkpoint: "automation-cutover:v1|full|delta|1", Actor: "operator", Now: secondNow,
	})
	require.ErrorIs(t, err, ErrAuthorityConflict)
	native, err := store.ActivateNativeAuthority(ctx, ActivateNativeAuthorityParams{
		WorkspaceID: "wsp_1", Domain: "automation", BindingKey: "team/repo",
		ExpectedGeneration: 0, ExpectedCheckpoint: second.Checkpoint,
		Checkpoint: "automation-cutover:v1|full|delta|1", Actor: "operator", Now: secondNow,
	})
	require.NoError(t, err)
	require.Equal(t, uint64(1), native.Generation)
	require.NoError(t, store.ReleaseLegacyAuthorityFence(ctx, second, secondNow), "release after native activation is a no-op")
}
