// Package testkit provides an API integration-test fixture around the explicit
// store graph. Packages that cannot import api/testkit directly due to import
// cycles (e.g. daemon, trigger) should use this package instead.
package testkit

import (
	"context"
	"fmt"
	"strings"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/archive"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store"
	"github.com/tidbcloud/AgentTeam/internal/store/admin"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/agentops"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/attachment"
	"github.com/tidbcloud/AgentTeam/internal/store/channel"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/knowledgeqa"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/store/message/testdelivery"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
	"github.com/tidbcloud/AgentTeam/internal/store/plan"
	"github.com/tidbcloud/AgentTeam/internal/store/privateagent"
	"github.com/tidbcloud/AgentTeam/internal/store/reaction"
	"github.com/tidbcloud/AgentTeam/internal/store/reporting"
	"github.com/tidbcloud/AgentTeam/internal/store/review"
	"github.com/tidbcloud/AgentTeam/internal/store/rewind"
	"github.com/tidbcloud/AgentTeam/internal/store/team"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

type StoreOptions = store.Options

type Store struct {
	Graph *store.Graph
	DB    coredb.DB
}

func New(db coredb.DB, archiveStorage archive.Storage, hotMessageRetentionDays int) *Store {
	return NewWithOptions(db, archiveStorage, hotMessageRetentionDays, StoreOptions{})
}

func NewWithOptions(db coredb.DB, archiveStorage archive.Storage, hotMessageRetentionDays int, options StoreOptions) *Store {
	return &Store{
		Graph: store.NewWithOptions(db, archiveStorage, nil, hotMessageRetentionDays, options),
		DB:    db,
	}
}

func Wrap(graph *store.Graph) *Store {
	if graph == nil {
		return nil
	}
	return &Store{Graph: graph}
}

func graph(root *Store) *store.Graph {
	if root == nil {
		return nil
	}
	return root.Graph
}

func (root *Store) Admin() *admin.Store {
	if g := graph(root); g != nil {
		return g.Admin
	}
	return nil
}

func (root *Store) Agent() *agent.Service {
	if g := graph(root); g != nil {
		return g.Agent
	}
	return nil
}

func (root *Store) AgentOps() *agentops.Store {
	if g := graph(root); g != nil {
		return g.AgentOps
	}
	return nil
}

func (root *Store) PrivateAgents() *privateagent.Store {
	if g := graph(root); g != nil {
		return g.PrivateAgents
	}
	return nil
}

func (root *Store) App() *app.Store {
	if g := graph(root); g != nil {
		return g.App
	}
	return nil
}

func (root *Store) Attachment() *attachment.Store {
	if g := graph(root); g != nil {
		return g.Attachment
	}
	return nil
}

func (root *Store) Channel() *channel.Store {
	if g := graph(root); g != nil {
		return g.Channel
	}
	return nil
}

func (root *Store) Identity() *identity.Store {
	if g := graph(root); g != nil {
		return g.Identity
	}
	return nil
}

func (root *Store) Message() *message.Store {
	if g := graph(root); g != nil {
		return g.Message
	}
	return nil
}

func (root *Store) KnowledgeQA() *knowledgeqa.Store {
	if g := graph(root); g != nil {
		return g.KnowledgeQA
	}
	return nil
}

func (root *Store) Plan() *plan.Store {
	if g := graph(root); g != nil {
		return g.Plan
	}
	return nil
}

func (root *Store) Reaction() *reaction.Store {
	if g := graph(root); g != nil {
		return g.Reaction
	}
	return nil
}

func (root *Store) Reporting() *reporting.Store {
	if g := graph(root); g != nil {
		return g.Reporting
	}
	return nil
}

func (root *Store) Rewind() *rewind.Store {
	if g := graph(root); g != nil {
		return g.Rewind
	}
	return nil
}

func (root *Store) Review() *review.Store {
	if g := graph(root); g != nil {
		return g.Review
	}
	return nil
}

func (root *Store) Team() *team.Store {
	if g := graph(root); g != nil {
		return g.Team
	}
	return nil
}

func (root *Store) Workspace() *workspace.Store {
	if g := graph(root); g != nil {
		return g.Workspace
	}
	return nil
}

func (root *Store) GetWorkspaceForUser(ctx context.Context, userID string, workspaceID string) (workspace.Summary, error) {
	return root.Workspace().GetWorkspaceForUser(ctx, userID, workspaceID)
}

func (root *Store) CreateWorkspaceInvite(ctx context.Context, userID string, workspaceID string, email string, tokenHash string, expiresAt time.Time) (string, error) {
	return root.Workspace().CreateWorkspaceInvite(ctx, userID, workspaceID, email, tokenHash, expiresAt)
}

func (root *Store) AcceptWorkspaceInvite(ctx context.Context, user model.User, workspaceID string, token string) (workspace.Summary, error) {
	return root.Workspace().AcceptWorkspaceInvite(ctx, user, workspaceID, token)
}

func (root *Store) SupportsMessageSearch(ctx context.Context) bool {
	return root.Message().SupportsMessageSearch(ctx)
}

func (root *Store) AuthenticateSession(ctx context.Context, token string) (identity.AuthenticatedUser, error) {
	return root.Identity().AuthenticateSession(ctx, token)
}

func (root *Store) CreateAgentForUser(ctx context.Context, userID string, workspaceID string, machineID string, params agent.CreateAgentParams) (agent.AgentView, error) {
	return root.App().CreateAgentForUser(ctx, userID, workspaceID, machineID, params)
}

func (root *Store) ListAgentsForWorkspace(ctx context.Context, userID string, workspaceID string) ([]agent.AppAgent, error) {
	return root.App().ListAgentsForWorkspace(ctx, userID, workspaceID)
}

func (root *Store) ListOwnedAgentsForWorkspace(ctx context.Context, userID string, workspaceID string) ([]agent.AppAgent, error) {
	return root.App().ListOwnedAgentsForWorkspace(ctx, userID, workspaceID)
}

func (root *Store) ListAgentDirectoryForWorkspace(ctx context.Context, userID string, workspaceID string, options agent.AgentDirectoryOptions) (agent.AgentDirectoryPage, error) {
	return root.App().ListAgentDirectoryForWorkspace(ctx, userID, workspaceID, options)
}

func (root *Store) ListAgentsForWorkspaceWithOptions(ctx context.Context, userID string, workspaceID string, options agent.AppAgentListOptions) ([]agent.AppAgent, error) {
	return root.App().ListAgentsForWorkspaceWithOptions(ctx, userID, workspaceID, options)
}

func (root *Store) AgentForWorkspace(ctx context.Context, userID string, workspaceID string, agentID string) (agent.AppAgent, error) {
	return root.App().AgentForWorkspace(ctx, userID, workspaceID, agentID)
}

func (root *Store) AgentDetailForWorkspace(ctx context.Context, userID string, workspaceID string, agentID string) (agent.AppAgent, error) {
	return root.App().AgentDetailForWorkspace(ctx, userID, workspaceID, agentID)
}

func (root *Store) AgentRuntimeFailureForWorkspace(ctx context.Context, userID string, workspaceID string, agentID string) (*agent.AgentRuntimeFailureSummary, error) {
	return root.App().AgentRuntimeFailureForWorkspace(ctx, userID, workspaceID, agentID)
}

func (root *Store) AgentRuntimeFailuresForWorkspace(ctx context.Context, userID string, workspaceID string, agentIDs []string) (map[string]agent.AgentRuntimeFailureSummary, error) {
	return root.App().AgentRuntimeFailuresForWorkspace(ctx, userID, workspaceID, agentIDs)
}

func (root *Store) GetAgentForUser(ctx context.Context, userID string, workspaceID string, agentID string) (agent.AgentView, error) {
	return root.App().GetAgentForUser(ctx, userID, workspaceID, agentID)
}

func (root *Store) UpdateAgentForUser(ctx context.Context, userID string, workspaceID string, agentID string, params agent.UpdateAgentParams) (agent.AgentView, error) {
	return root.App().UpdateAgentForUser(ctx, userID, workspaceID, agentID, params)
}

func (root *Store) DeleteAgentForWorkspace(ctx context.Context, userID string, workspaceID string, agentID string) (string, error) {
	return root.App().DeleteAgentForWorkspace(ctx, userID, workspaceID, agentID)
}

func (root *Store) SetAgentAutoWakeForUser(ctx context.Context, userID string, workspaceID string, agentID string, autoWake bool) (agent.AgentView, error) {
	return root.App().SetAgentAutoWakeForUser(ctx, userID, workspaceID, agentID, autoWake)
}

func (root *Store) GetAgentWorkspaceReaderForUser(ctx context.Context, userID string, workspaceID string, agentID string) (agent.AgentView, error) {
	return root.App().GetAgentWorkspaceReaderForUser(ctx, userID, workspaceID, agentID)
}

func (root *Store) ClearAgentSessionForWorkspace(ctx context.Context, userID string, workspaceID string, agentID string) error {
	return root.App().ClearAgentSessionForWorkspace(ctx, userID, workspaceID, agentID)
}

func (root *Store) AddChannelAgentForUser(ctx context.Context, userID string, workspaceID string, channelID string, agentID string) error {
	return root.App().AddChannelAgentForUser(ctx, userID, workspaceID, channelID, agentID)
}

func (root *Store) JoinChannelForUser(ctx context.Context, userID string, workspaceID string, channelID string) error {
	return root.Channel().JoinChannelForUser(ctx, userID, workspaceID, channelID)
}

func (root *Store) AuthenticateAgent(ctx context.Context, agentID string, bearerToken string) (agent.AuthenticatedAgent, error) {
	return root.Agent().AuthenticateAgent(ctx, agentID, bearerToken)
}

func (root *Store) ResolveWritableAgentTargetScopeID(ctx context.Context, authenticated agent.AuthenticatedAgent, target string) (string, error) {
	return root.App().ResolveWritableAgentTargetScopeID(ctx, authenticated, target)
}

func (root *Store) ResolveReadableAgentTargetScopeID(ctx context.Context, authenticated agent.AuthenticatedAgent, target string) (string, error) {
	return root.App().ResolveReadableAgentTargetScopeID(ctx, authenticated, target)
}

func (root *Store) ResolveAgentReviewSubjectScopeID(ctx context.Context, authenticated agent.AuthenticatedAgent, target string) (string, error) {
	return root.App().ResolveAgentReviewSubjectScopeID(ctx, authenticated, target)
}

func (root *Store) SendAgentMessage(ctx context.Context, sender agent.AuthenticatedAgent, params message.SendMessageParams) (message.MessageDelivery, error) {
	return root.App().SendAgentThreadMessage(ctx, sender, params)
}

func (root *Store) ReceiveAgentMessages(ctx context.Context, authenticated agent.AuthenticatedAgent, limit int) ([]message.ReceiveMessage, error) {
	return testdelivery.ReceiveAgentMessagesFromFanout(ctx, root.DB, authenticated, limit)
}

func (root *Store) ResolveChannelAgentRecipientByIDForAgent(ctx context.Context, authenticated agent.AuthenticatedAgent, channelID string, agentID string) (message.AgentRecipient, error) {
	return root.App().ResolveChannelAgentRecipientByIDForAgent(ctx, authenticated, channelID, agentID)
}

func (root *Store) AgentManagedByUser(ctx context.Context, workspaceID string, agentID string) (string, error) {
	return root.App().AgentManagedByUser(ctx, workspaceID, agentID)
}

func (root *Store) FindUserByID(ctx context.Context, userID string) (identity.User, error) {
	return root.Identity().FindUserByID(ctx, userID)
}

func (root *Store) GetUserPingCAPAuthBinding(ctx context.Context, userID string) (identity.UserPingCAPAuthBinding, error) {
	return root.Identity().GetUserPingCAPAuthBinding(ctx, userID)
}

func (root *Store) AgentHasUnreadMessages(ctx context.Context, authenticated agent.AuthenticatedAgent) (bool, error) {
	return testdelivery.AgentHasUnreadFanoutMessages(ctx, root.DB, authenticated)
}

func (root *Store) GetAgentWorkspaceSnapshot(ctx context.Context, authenticated agent.AuthenticatedAgent) (agent.WorkspaceSnapshot, error) {
	return root.App().GetAgentWorkspaceSnapshot(ctx, authenticated)
}

func (root *Store) OpenReplyThreadForUser(ctx context.Context, userID string, workspaceID string, channelID string, parentMessageID string) (message.ReplyThreadSummary, error) {
	return root.Message().OpenReplyThreadForUser(ctx, userID, workspaceID, channelID, parentMessageID)
}

func (root *Store) PrepareAgentStart(ctx context.Context, agentID string, publicBaseURL string, wakeMessage *message.ReceiveMessage, allowWorkspaceWrite bool, includeLegacySession bool, _ bool) (agent.StartCommand, error) {
	prepared, err := root.Agent().PrepareAgentStart(ctx, agentID, allowWorkspaceWrite, includeLegacySession)
	if err != nil {
		return agent.StartCommand{}, err
	}

	agentName := strings.TrimSpace(prepared.AgentID)
	if root != nil && root.DB != nil {
		var loadedName string
		if err := root.DB.QueryRow(ctx, `
SELECT name
FROM agents
WHERE id = $1 AND workspace_id = $2 AND member_id = $3
`, prepared.AgentID, prepared.WorkspaceID, prepared.MemberID).Scan(&loadedName); err != nil {
			return agent.StartCommand{}, fmt.Errorf("load agent public name: %w", err)
		}
		if strings.TrimSpace(loadedName) != "" {
			agentName = strings.TrimSpace(loadedName)
		}
	}

	unreadThreads, err := root.Message().ListAgentUnreadMessageThreads(ctx, prepared.WorkspaceID, prepared.MemberID)
	if err != nil {
		return agent.StartCommand{}, fmt.Errorf("load agent unread summary: %w", err)
	}
	materialization, err := root.Agent().LoadAgentWorkspaceMaterialization(ctx, prepared.AgentID)
	if err != nil {
		return agent.StartCommand{}, fmt.Errorf("load agent materialization: %w", err)
	}

	return agent.StartCommand{
		AgentID: prepared.AgentID,
		Config: agent.StartConfig{
			Name:                        agentName,
			DisplayName:                 prepared.DisplayName,
			Description:                 prepared.Description,
			Runtime:                     prepared.Runtime,
			PermissionMode:              prepared.PermissionMode,
			RuntimePolicy:               agent.NewRuntimePolicySnapshot(prepared.Runtime, prepared.PermissionMode, prepared.RuntimePolicy.Generation, prepared.VisibleReplyContractEnabled),
			VisibleReplyContractEnabled: prepared.VisibleReplyContractEnabled,
			RuntimeDebugEnabled:         prepared.RuntimeDebugEnabled,
			Model:                       prepared.Model,
			ReasoningEffort:             prepared.ReasoningEffort,
			ServerURL:                   publicBaseURL,
			AuthToken:                   prepared.AuthToken,
			SessionID:                   prepared.SessionID,
			Materialization:             materialization,
		},
		WakeMessage:   wakeMessage,
		UnreadSummary: message.BuildAgentUnreadSummary(unreadThreads),
	}, nil
}
