package webagent

import (
	"context"
	"sync"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/service/agentmemory"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/rewind"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

type fakeStore struct {
	mu sync.Mutex

	authenticated   identity.AuthenticatedUser
	authenticateErr error
	workspace       workspace.Summary
	workspaceErr    error

	agents               []agent.AppAgent
	ownedAgents          []agent.AppAgent
	agentsErr            error
	listAgentsCalls      int
	listOwnedAgentsCalls int
	directoryPage        agent.AgentDirectoryPage
	directoryErr         error
	lastDirectory        agent.AgentDirectoryOptions
	createdAgent         agent.AgentView
	createErr            error
	lastCreate           createAgentCall
	lastListOptions      agent.AppAgentListOptions

	runtimeSnapshots       []agent.AgentRuntimeSnapshot
	runtimeSnapshotsErr    error
	lastRuntimeSnapshotIDs []string
	runtimeFailures        map[string]agent.AgentRuntimeFailureSummary
	runtimeFailuresErr     error
	runtimeFailure         *agent.AgentRuntimeFailureSummary
	runtimeFailureErr      error
	lastRuntimeFailureID   string
	lastRuntimeFailureIDs  []string

	appAgents               map[string]agent.AppAgent
	appAgentErr             error
	appAgentErrByID         map[string]error
	lastAppAgentOptions     agent.AppAgentDetailOptions
	agentView               agent.AgentView
	getAgentErr             error
	lastCopyRoleBinding     copyRoleBindingCall
	copyRoleBindingErr      error
	lastRoleMaterialization agent.AgentRoleMaterializationInput
	roleMaterializationErr  error
	updateResult            agent.AgentView
	updateErr               error
	lastUpdateID            string
	lastUpdate              agent.UpdateAgentParams
	updateCalls             []updateAgentCall
	restoreCalls            []restorePermissionCall
	restoreErr              error
	recoveryPoints          []rewind.MessageRecoveryPoint
	recoveryErr             error
	deleteMachine           string
	deleteErr               error
	lastDeleteID            string

	autoWakeAgent     agent.AgentView
	autoWakeErr       error
	lastAutoWakeID    string
	lastAutoWake      bool
	workspaceAgent    agent.AgentView
	workspaceAgentErr error
	deletedRecovery   []rewind.MessageRecoveryPoint
	deleteRecoveryErr error
	clearSessionErr   error
	lastClearSession  string

	reviewReader    agent.AgentView
	reviewReaderErr error
	reviews         []agent.ReviewContext
	reviewsErr      error
	review          *agent.ReviewContext
	reviewErr       error
	upsertReview    agent.ReviewContext
	upsertReviewErr error
	lastReviewID    string
	reviewLink      *agent.ReviewContext
	reviewLinkErr   error
	lastScopeType   string
	lastScopeID     string
	lastAgentIDs    []string

	projects               []agent.Project
	projectsErr            error
	project                agent.Project
	projectErr             error
	lastProjectID          string
	lastProjectParams      agent.CreateProjectParams
	lastProjectUpdate      agent.UpdateProjectParams
	lastProjectListOptions agent.ProjectListOptions
	projectPage            agent.ProjectPage
	createProjectErr       error
	updateProjectErr       error
	deleteProjectErr       error
	projectGrants          []agent.ProjectGrant
	projectGrantsErr       error
	projectGrant           agent.ProjectGrant
	upsertGrantErr         error
	lastGrantMemberID      string
	lastGrantRole          string
	deleteGrantErr         error
	machineAccessErr       error
	lastMachineAccessCheck string

	modelProfiles       []agent.ModelProfile
	modelProfilesErr    error
	modelProfile        agent.ModelProfile
	modelProfileErr     error
	createdProfiles     []agent.ModelProfile
	createProfileErr    error
	lastCreateProfile   agent.CreateModelProfileParams
	updatedProfiles     []agent.ModelProfile
	updateProfileErr    error
	lastUpdateProfileID string
	lastUpdateProfile   agent.UpdateModelProfileParams
	deleteProfileErr    error
	lastDeleteProfile   string

	behaviorSettings agent.BehaviorSettings
}

type createAgentCall struct {
	UserID      string
	WorkspaceID string
	MachineID   string
	Params      agent.CreateAgentParams
}

type updateAgentCall struct {
	AgentID string
	Params  agent.UpdateAgentParams
}

type copyRoleBindingCall struct {
	SourceAgentID string
	TargetAgentID string
}

type restorePermissionCall struct {
	AgentID                string
	PermissionMode         string
	ExpectedPermissionMode string
}

type fakeCloudMemoryProvisioner struct {
	err   error
	calls []agentmemory.EnableInput
}

func (fake *fakeCloudMemoryProvisioner) EnableAgentCloudMemory(_ context.Context, input agentmemory.EnableInput) error {
	fake.calls = append(fake.calls, input)
	return fake.err
}

func newFakeStore() *fakeStore {
	now := time.Now().UTC()
	agentView := agent.AgentView{
		AgentID:         "agt_1",
		MemberID:        "mem_agent_1",
		DisplayName:     "Agent One",
		Runtime:         "codex",
		Model:           "gpt-5.4",
		ReasoningEffort: "medium",
		MachineID:       "mach_1",
		AutoWakeEnabled: true,
		Status:          "active",
	}
	appAgent := agent.AppAgent{ID: "agt_1", Name: "agent-one", DisplayName: "Agent One", Runtime: "codex", Model: "gpt-5.4", MachineID: "mach_1", Status: "active"}
	return &fakeStore{
		authenticated:  identity.AuthenticatedUser{User: identity.User{ID: "usr_1", Name: "alice", Email: "alice@example.com", EmailVerifiedAt: &now}},
		workspace:      workspace.Summary{WorkspaceID: "wsp_1", Slug: "ws"},
		createdAgent:   agentView,
		appAgents:      map[string]agent.AppAgent{"agt_1": appAgent, "agt_new": {ID: "agt_new", Name: "new-agent", DisplayName: "New Agent", MachineID: "mach_1"}},
		agentView:      agentView,
		updateResult:   agentView,
		autoWakeAgent:  agentView,
		workspaceAgent: agentView,
		reviewReader:   agentView,
		deleteMachine:  "mach_1",
	}
}

func (fake *fakeStore) AuthenticateSession(context.Context, string) (identity.AuthenticatedUser, error) {
	if fake.authenticateErr != nil {
		return identity.AuthenticatedUser{}, fake.authenticateErr
	}
	return fake.authenticated, nil
}

func (fake *fakeStore) GetWorkspaceForUser(context.Context, string, string) (workspace.Summary, error) {
	if fake.workspaceErr != nil {
		return workspace.Summary{}, fake.workspaceErr
	}
	return fake.workspace, nil
}

func (fake *fakeStore) AgentBehaviorSettingsForUser(_ context.Context, _ string, workspaceID string, agentID string) (agent.BehaviorSettings, error) {
	if fake.behaviorSettings.AgentID != "" {
		return fake.behaviorSettings, nil
	}
	return agent.BehaviorSettings{
		WorkspaceID:                   workspaceID,
		AgentID:                       agentID,
		AccountVisibleReplyContract:   true,
		VisibleReplyContractEnabled:   false,
		EffectiveVisibleReplyContract: false,
	}, nil
}

func (fake *fakeStore) ListAgentsForWorkspace(context.Context, string, string) ([]agent.AppAgent, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.listAgentsCalls++
	return fake.agents, fake.agentsErr
}

func (fake *fakeStore) ListOwnedAgentsForWorkspace(context.Context, string, string) ([]agent.AppAgent, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.listOwnedAgentsCalls++
	return fake.ownedAgents, fake.agentsErr
}

func (fake *fakeStore) ListAgentsForWorkspaceWithOptions(_ context.Context, _ string, _ string, options agent.AppAgentListOptions) ([]agent.AppAgent, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastListOptions = options
	if options.OwnedOnly {
		return fake.ownedAgents, fake.agentsErr
	}
	return fake.agents, fake.agentsErr
}

func (fake *fakeStore) AgentRuntimeSnapshotsForWorkspace(_ context.Context, _ string, _ string, agentIDs []string) ([]agent.AgentRuntimeSnapshot, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastRuntimeSnapshotIDs = append([]string(nil), agentIDs...)
	return fake.runtimeSnapshots, fake.runtimeSnapshotsErr
}

func (fake *fakeStore) CreateAgentForUser(_ context.Context, userID, workspaceID, machineID string, params agent.CreateAgentParams) (agent.AgentView, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastCreate = createAgentCall{UserID: userID, WorkspaceID: workspaceID, MachineID: machineID, Params: params}
	if fake.createErr != nil {
		return agent.AgentView{}, fake.createErr
	}
	if params.AutoWakeEnabled != nil {
		fake.createdAgent.AutoWakeEnabled = *params.AutoWakeEnabled
	}
	return fake.createdAgent, nil
}

func (fake *fakeStore) AgentForWorkspace(_ context.Context, _ string, _ string, agentID string, options ...agent.AppAgentDetailOptions) (agent.AppAgent, error) {
	fake.lastAppAgentOptions = agent.AppAgentDetailOptions{IncludeRuntimeFailure: true}
	if len(options) > 0 {
		fake.lastAppAgentOptions = options[0]
	}
	return fake.agentForWorkspace(agentID)
}

func (fake *fakeStore) agentForWorkspace(agentID string) (agent.AppAgent, error) {
	if err := fake.appAgentErrByID[agentID]; err != nil {
		return agent.AppAgent{}, err
	}
	if fake.appAgentErr != nil {
		return agent.AppAgent{}, fake.appAgentErr
	}
	if fake.appAgents != nil {
		if agent, ok := fake.appAgents[agentID]; ok {
			return agent, nil
		}
	}
	return agent.AppAgent{ID: agentID, MachineID: "mach_1"}, nil
}

func (fake *fakeStore) GetAgentForUser(context.Context, string, string, string) (agent.AgentView, error) {
	if fake.getAgentErr != nil {
		return agent.AgentView{}, fake.getAgentErr
	}
	return fake.agentView, nil
}

func (fake *fakeStore) CopyAgentRoleBindingForUser(_ context.Context, _ string, _ string, sourceAgentID string, targetAgentID string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastCopyRoleBinding = copyRoleBindingCall{SourceAgentID: sourceAgentID, TargetAgentID: targetAgentID}
	return fake.copyRoleBindingErr
}

func (fake *fakeStore) UpsertAgentRoleMaterialization(_ context.Context, input agent.AgentRoleMaterializationInput) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastRoleMaterialization = input
	return fake.roleMaterializationErr
}

func (fake *fakeStore) UpdateAgentForUser(_ context.Context, _ string, _ string, agentID string, params agent.UpdateAgentParams) (agent.AgentView, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastUpdateID = agentID
	fake.lastUpdate = params
	fake.updateCalls = append(fake.updateCalls, updateAgentCall{AgentID: agentID, Params: params})
	if fake.updateErr != nil {
		return agent.AgentView{}, fake.updateErr
	}
	return fake.updateResult, nil
}

func (fake *fakeStore) RestoreAgentPermissionModeForUser(_ context.Context, _ string, _ string, agentID string, permissionMode string, expectedPermissionMode string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.restoreCalls = append(fake.restoreCalls, restorePermissionCall{AgentID: agentID, PermissionMode: permissionMode, ExpectedPermissionMode: expectedPermissionMode})
	return fake.restoreErr
}

func (fake *fakeStore) ListAllAgentRecoveryPointsForWorkspace(context.Context, string, string, string) ([]rewind.MessageRecoveryPoint, error) {
	return fake.recoveryPoints, fake.recoveryErr
}

func (fake *fakeStore) DeleteAgentForWorkspace(_ context.Context, _ string, _ string, agentID string) (string, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastDeleteID = agentID
	return fake.deleteMachine, fake.deleteErr
}

func (fake *fakeStore) ListModelProfilesForUser(context.Context, string) ([]agent.ModelProfile, error) {
	return fake.modelProfiles, fake.modelProfilesErr
}

func (fake *fakeStore) CreateModelProfileForUser(_ context.Context, _ string, params agent.CreateModelProfileParams) ([]agent.ModelProfile, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastCreateProfile = params
	if fake.createProfileErr != nil {
		return nil, fake.createProfileErr
	}
	return fake.createdProfiles, nil
}

func (fake *fakeStore) GetModelProfileForUser(context.Context, string, string) (agent.ModelProfile, error) {
	return fake.modelProfile, fake.modelProfileErr
}

func (fake *fakeStore) UpdateModelProfileForUser(_ context.Context, _ string, profileID string, params agent.UpdateModelProfileParams) ([]agent.ModelProfile, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastUpdateProfileID = profileID
	fake.lastUpdateProfile = params
	if fake.updateProfileErr != nil {
		return nil, fake.updateProfileErr
	}
	return fake.updatedProfiles, nil
}

func (fake *fakeStore) DeleteModelProfileForUser(_ context.Context, _ string, profileID string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastDeleteProfile = profileID
	return fake.deleteProfileErr
}

func (fake *fakeStore) SetAgentAutoWakeForUser(_ context.Context, _ string, _ string, agentID string, autoWake bool) (agent.AgentView, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastAutoWakeID = agentID
	fake.lastAutoWake = autoWake
	if fake.autoWakeErr != nil {
		return agent.AgentView{}, fake.autoWakeErr
	}
	fake.autoWakeAgent.AutoWakeEnabled = autoWake
	return fake.autoWakeAgent, nil
}

func (fake *fakeStore) GetAgentWorkspaceReaderForUser(context.Context, string, string, string) (agent.AgentView, error) {
	if fake.workspaceAgentErr != nil {
		return agent.AgentView{}, fake.workspaceAgentErr
	}
	return fake.workspaceAgent, nil
}

func (fake *fakeStore) DeleteAllAgentRecoveryPointsForWorkspace(context.Context, string, string, string) ([]rewind.MessageRecoveryPoint, error) {
	return fake.deletedRecovery, fake.deleteRecoveryErr
}

func (fake *fakeStore) ClearAgentSessionForWorkspace(_ context.Context, _ string, _ string, agentID string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastClearSession = agentID
	return fake.clearSessionErr
}

func (fake *fakeStore) GetAgentReviewReaderForUser(context.Context, string, string, string) (agent.AgentView, error) {
	if fake.reviewReaderErr != nil {
		return agent.AgentView{}, fake.reviewReaderErr
	}
	return fake.reviewReader, nil
}

func (fake *fakeStore) RequireMachineManagementForUser(_ context.Context, _ string, _ string, machineID string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastMachineAccessCheck = machineID
	if fake.machineAccessErr != nil {
		return fake.machineAccessErr
	}
	return nil
}

func (fake *fakeStore) ListVisibleReviewContextsForUser(context.Context, string, string) ([]agent.ReviewContext, error) {
	return fake.reviews, fake.reviewsErr
}

func (fake *fakeStore) ListReviewContextsForProject(_ context.Context, _ string, projectID string) ([]agent.ReviewContext, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectID = projectID
	return fake.reviews, fake.reviewsErr
}

func (fake *fakeStore) ListReviewContextsForThread(_ context.Context, _ string, _ string, _ string) ([]agent.ReviewContext, error) {
	return fake.reviews, fake.reviewsErr
}

func (fake *fakeStore) GetReviewContextForUser(_ context.Context, _ string, _ string, reviewID string) (*agent.ReviewContext, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastReviewID = reviewID
	return fake.review, fake.reviewErr
}

func (fake *fakeStore) DeleteReviewContextForUser(_ context.Context, _ string, _ string, reviewID string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastReviewID = reviewID
	return fake.reviewErr
}

func (fake *fakeStore) CreateReviewContextForUser(_ context.Context, _ string, params agent.UpsertReviewContextParams) (agent.ReviewContext, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	if fake.upsertReviewErr != nil {
		return agent.ReviewContext{}, fake.upsertReviewErr
	}
	if fake.upsertReview.ID == "" {
		fake.upsertReview = agent.ReviewContext{ID: "rcx_upserted", WorkspaceID: params.WorkspaceID, ThreadID: params.ThreadID, SourceAgentID: params.SourceAgentID, MachineID: params.MachineID, RootPath: params.RootPath, BaseRef: params.BaseRef, HeadRef: params.HeadRef, DisplayName: params.DisplayName, PRURL: params.PRURL}
	}
	return fake.upsertReview, nil
}

func (fake *fakeStore) UpdateReviewContextForUser(_ context.Context, _ string, _ string, reviewID string, _ agent.UpsertReviewContextParams) (*agent.ReviewContext, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastReviewID = reviewID
	return fake.review, fake.reviewErr
}

func (fake *fakeStore) ListProjectsForUser(_ context.Context, _ string, _ string) ([]agent.Project, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	return fake.projects, fake.projectsErr
}

func (fake *fakeStore) ListProjectPageForUser(_ context.Context, _ string, _ string, options agent.ProjectListOptions) (agent.ProjectPage, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectListOptions = options
	if fake.projectsErr != nil {
		return agent.ProjectPage{}, fake.projectsErr
	}
	return fake.projectPage, nil
}

func (fake *fakeStore) CreateProjectForUser(_ context.Context, _ string, params agent.CreateProjectParams) (agent.Project, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectParams = params
	if fake.createProjectErr != nil {
		return agent.Project{}, fake.createProjectErr
	}
	if fake.project.ID == "" {
		fake.project = agent.Project{
			ID:          "prj_new",
			WorkspaceID: params.WorkspaceID,
			MachineID:   params.MachineID,
			RootPath:    params.RootPath,
			Name:        params.Name,
			Description: params.Description,
			Visibility:  params.Visibility,
		}
	}
	return fake.project, nil
}

func (fake *fakeStore) GetProjectForUser(_ context.Context, _ string, _ string, projectID string) (agent.Project, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectID = projectID
	return fake.project, fake.projectErr
}

func (fake *fakeStore) UpdateProjectForUser(_ context.Context, _ string, _ string, projectID string, params agent.UpdateProjectParams) (agent.Project, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectID = projectID
	fake.lastProjectUpdate = params
	if fake.updateProjectErr != nil {
		return agent.Project{}, fake.updateProjectErr
	}
	return fake.project, nil
}

func (fake *fakeStore) DeleteProjectForUser(_ context.Context, _ string, _ string, projectID string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectID = projectID
	return fake.deleteProjectErr
}

func (fake *fakeStore) ListProjectAccessForUser(_ context.Context, _ string, _ string, projectID string) ([]agent.ProjectGrant, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectID = projectID
	return fake.projectGrants, fake.projectGrantsErr
}

func (fake *fakeStore) UpsertProjectGrantForUser(_ context.Context, _ string, _ string, projectID string, memberID string, role string) (agent.ProjectGrant, error) {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectID = projectID
	fake.lastGrantMemberID = memberID
	fake.lastGrantRole = role
	if fake.upsertGrantErr != nil {
		return agent.ProjectGrant{}, fake.upsertGrantErr
	}
	return fake.projectGrant, nil
}

func (fake *fakeStore) DeleteProjectGrantForUser(_ context.Context, _ string, _ string, projectID string, memberID string) error {
	fake.mu.Lock()
	defer fake.mu.Unlock()
	fake.lastProjectID = projectID
	fake.lastGrantMemberID = memberID
	return fake.deleteGrantErr
}
