package runtime

import (
	"context"
	"encoding/json"
	"testing"
	"time"

	"github.com/stretchr/testify/require"
)

type fakeAuthorizedRootCatalog struct {
	candidates []AuthorizedRootCandidate
	request    ScopeRequest
}

func (catalog *fakeAuthorizedRootCatalog) ResolveAuthorizedRoots(_ context.Context, request ScopeRequest) ([]AuthorizedRootCandidate, error) {
	catalog.request = request
	return append([]AuthorizedRootCandidate(nil), catalog.candidates...), nil
}

func TestResolverPersistsRequesterBoundLayeredRoots(t *testing.T) {
	now := time.Date(2026, 7, 25, 10, 0, 0, 0, time.UTC)
	catalog := &fakeAuthorizedRootCatalog{candidates: []AuthorizedRootCandidate{
		{KnowledgeBaseID: "kb_1", RootKind: RootKindLexicon, RepoRole: RepoRoleWiki, Revision: "wiki-sha", RepositoryRef: "repo-wiki"},
		{KnowledgeBaseID: "kb_1", SourceID: "src_1", RootKind: RootKindInsights, RepoRole: RepoRoleInsights, Revision: "insights-sha", RepositoryRef: "repo-insights"},
		{KnowledgeBaseID: "kb_1", SourceID: "src_1", RootKind: RootKindPrimary, RepoRole: RepoRoleMirror, Revision: "source-sha", RepositoryRef: "repo-source", DefaultRef: "main", FreshnessStatus: "current"},
	}}
	handles := &memoryHandleStore{}
	resolver := &Resolver{
		Catalog: catalog, Handles: handles, Clock: func() time.Time { return now }, HandleTTL: 5 * time.Minute,
	}
	views, err := resolver.Resolve(context.Background(), ScopeRequest{
		Principal: testPrincipal(), KnowledgeBaseIDs: []string{"kb_1", "kb_1"}, SourceIDs: []string{"src_1"},
	})
	require.NoError(t, err)
	require.Len(t, views, 3)
	require.Equal(t, []string{"kb_1"}, catalog.request.KnowledgeBaseIDs)
	require.Len(t, handles.handles, 3)
	for _, handle := range handles.handles {
		require.Equal(t, "org_1", handle.OrganizationID)
		require.Equal(t, "wsp_1", handle.WorkspaceID)
		require.Equal(t, "usr_1", handle.RequesterID)
		require.Equal(t, "kqt_1", handle.TurnID)
		require.Equal(t, now.Add(5*time.Minute), handle.ExpiresAt)
	}
	for _, handle := range handles.handles {
		if handle.RootKind == RootKindPrimary {
			require.Equal(t, "main", handle.ResolvedRef)
			require.Equal(t, RefKindBranch, handle.ResolvedRefKind)
		}
	}
	raw, err := json.Marshal(views)
	require.NoError(t, err)
	require.NotContains(t, string(raw), "repo-wiki")
	require.NotContains(t, string(raw), "repo-source")
}

func TestResolverDefaultHandleTTLCoversCompleteTurnExecutionWindow(t *testing.T) {
	require.Equal(t, 20*time.Minute, DefaultTurnExecutionTimeout)
	require.Equal(t, DefaultTurnExecutionTimeout+time.Minute, DefaultRootHandleTTL)
	require.Equal(t, DefaultRootHandleTTL, (&Resolver{}).handleTTL())
}

func TestResolverRejectsLexiconThatImpersonatesSource(t *testing.T) {
	now := time.Date(2026, 7, 25, 10, 0, 0, 0, time.UTC)
	resolver := &Resolver{
		Catalog: &fakeAuthorizedRootCatalog{candidates: []AuthorizedRootCandidate{{
			KnowledgeBaseID: "kb_1", SourceID: "src_1", RootKind: RootKindLexicon,
			RepoRole: RepoRoleWiki, Revision: "sha", RepositoryRef: "repo",
		}}},
		Handles: &memoryHandleStore{},
		Clock:   func() time.Time { return now },
	}
	_, err := resolver.Resolve(context.Background(), ScopeRequest{Principal: testPrincipal()})
	var toolErr *Error
	require.ErrorAs(t, err, &toolErr)
	require.Equal(t, CodeRepositoryFailed, toolErr.Code)
}
