package review

import (
	"context"
	"os"
	"testing"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/coredb/testkit"
	"github.com/tidbcloud/AgentTeam/internal/store/access"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

// Package-level TiDB fixture. Rollback-only callers share one migrated database
// and run serially; scenario families may share one outer transaction when
// their identifiers are distinct. Real concurrency tests keep using their own
// independently cloned database. The package boundary guard rejects parallel
// tests and state that transaction rollback cannot isolate.
var reviewTiDB testkit.TiDBCoreDBFixture

func TestMain(m *testing.M) {
	os.Exit(testkit.RunTestsWithTiDBCoreDBFixture(m, "review", &reviewTiDB))
}

func startReviewRollbackDB(t *testing.T, ctx context.Context) coredb.DB {
	t.Helper()
	return reviewTiDB.SerializedRollbackDB(t, ctx)
}

// permissiveOptionsNoMemberResolver returns Options with all access callbacks
// stubbed permissively EXCEPT LoadHumanWorkspaceMember, which is left nil so
// the wrapper falls back to access.LoadHumanWorkspaceMember and reads the real
// members table. Used by project tests that seed real workspace members via
// seedProjectTestWorkspace and need role/ownership checks to resolve them.
func permissiveOptionsNoMemberResolver() Options {
	return Options{
		LoadAuthorizedHumanThreadAccess: func(ctx context.Context, _ Queryer, _, _, _ string, _ access.HumanAction) (access.HumanThreadAccess, error) {
			return access.HumanThreadAccess{}, nil
		},
		RequireAgentReviewRead: func(ctx context.Context, _, _, _ string) (access.HumanAgentAccess, error) {
			return access.HumanAgentAccess{}, nil
		},
		RequireAgentOperation: func(ctx context.Context, _, _, _ string) (access.HumanAgentAccess, error) {
			return access.HumanAgentAccess{}, nil
		},
		AgentByID: func(ctx context.Context, _ Queryer, _, _ string) (agent.AgentView, error) {
			return agent.AgentView{}, nil
		},
	}
}

// newReviewStoreWithPermissiveAccess builds a *Store wired with stub access
// helpers that always grant. The store-level review-context mutators
// (UpdateReviewContextForUser, DeleteReviewContextForUser) route thread/source
// authorization through Options; for the target_hash regression we do not
// care about access decisions, only the SQL/identity behavior, so a
// permissive stub is the cleanest setup.
//
// LoadHumanWorkspaceMember is also stubbed to return a synthetic member keyed
// by the caller's userID so the C4.3 review→project resolver
// (GetOrCreateProjectForUser) can run without seeding real workspace members.
// Tests that exercise real per-user ownership semantics use
// seedProjectTestWorkspace + newProjectTestStore instead.
func newReviewStoreWithPermissiveAccess(db coredb.DB) *Store {
	opts := permissiveOptionsNoMemberResolver()
	opts.LoadHumanWorkspaceMember = func(ctx context.Context, _ Queryer, userID, workspaceID string) (access.HumanWorkspaceMember, error) {
		// Derive a stable member id from userID so repeated calls for the
		// same user resolve to the same owner — needed for project
		// get-or-create dedup.
		memberID := "mem_stub_" + userID
		userIDCopy := userID
		role := domain.WorkspaceRoleAdmin
		memberType := domain.MemberTypeHuman
		return access.HumanWorkspaceMember{
			Member: model.Member{
				ID:            memberID,
				WorkspaceID:   workspaceID,
				Type:          memberType,
				DisplayName:   userID,
				UserID:        &userIDCopy,
				WorkspaceRole: &role,
			},
			WorkspaceRole: "admin",
		}, nil
	}
	return New(db, opts)
}

// newReviewStoreWithRealMemberResolver matches newReviewStoreWithPermissiveAccess
// but does NOT stub LoadHumanWorkspaceMember, so project authorization reads
// the real members table. Project tests use this via newProjectTestStore.
func newReviewStoreWithRealMemberResolver(db coredb.DB) *Store {
	return New(db, permissiveOptionsNoMemberResolver())
}
