package workspace

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/app/runtime"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
)

type fakeWorkspaceStore struct {
	listUserID               string
	getArgs                  []string
	updateArgs               []string
	inviteArgs               []any
	channelSidebarGetArgs    []string
	channelSidebarPutArgs    []string
	channelSidebarPreference workspace.ChannelSidebarPreference
}

func (store *fakeWorkspaceStore) ListUserWorkspaces(_ context.Context, userID string) ([]workspace.Summary, error) {
	store.listUserID = userID
	return []workspace.Summary{{WorkspaceID: "wsp_1", Name: "Acme"}}, nil
}

func (store *fakeWorkspaceStore) GetWorkspaceForUser(_ context.Context, userID string, workspaceID string) (workspace.Summary, error) {
	store.getArgs = []string{userID, workspaceID}
	return workspace.Summary{WorkspaceID: workspaceID, MemberID: "mem_1"}, nil
}

func (store *fakeWorkspaceStore) GetChannelSidebarPreferenceForUser(_ context.Context, userID string, workspaceID string) (workspace.ChannelSidebarPreference, bool, error) {
	store.channelSidebarGetArgs = []string{userID, workspaceID}
	return store.channelSidebarPreference, true, nil
}

func (store *fakeWorkspaceStore) PutChannelSidebarPreferenceForUser(_ context.Context, userID string, workspaceID string, state workspace.ChannelSidebarPreferenceState, expectedRevision *int64) (workspace.ChannelSidebarPreference, error) {
	store.channelSidebarPutArgs = []string{userID, workspaceID}
	store.channelSidebarPreference.State = state
	if expectedRevision == nil {
		store.channelSidebarPreference.Revision = 1
	} else {
		store.channelSidebarPreference.Revision = *expectedRevision + 1
	}
	return store.channelSidebarPreference, nil
}

func (store *fakeWorkspaceStore) UpdateWorkspaceNameForUser(_ context.Context, userID string, workspaceID string, name string) (workspace.Summary, error) {
	store.updateArgs = []string{userID, workspaceID, name}
	return workspace.Summary{WorkspaceID: workspaceID, Name: name}, nil
}

func (store *fakeWorkspaceStore) CreateWorkspaceInvite(_ context.Context, inviterUserID string, workspaceID string, email string, tokenHash string, expiresAt time.Time) (string, error) {
	store.inviteArgs = []any{inviterUserID, workspaceID, email, tokenHash, expiresAt}
	return "invite_1", nil
}

func (store *fakeWorkspaceStore) CreateWorkspaceMobileInvite(_ context.Context, inviterUserID string, workspaceID string, mobile string, tokenHash string, expiresAt time.Time) (string, error) {
	store.inviteArgs = []any{inviterUserID, workspaceID, mobile, tokenHash, expiresAt}
	return "invite_1", nil
}

type fakeWorkspaceAdminStore struct {
	roleArgs      []string
	removeArgs    []string
	impactArgs    []string
	manageArgs    []string
	memberOptions workspace.WorkspaceMemberListOptions
	lookupOptions workspace.WorkspaceMemberLookupOptions
}

func (store *fakeWorkspaceAdminStore) ListWorkspaceMembersForUser(_ context.Context, _ string, _ string, options workspace.WorkspaceMemberListOptions) (workspace.WorkspaceMemberPage, error) {
	store.memberOptions = options
	return workspace.WorkspaceMemberPage{Members: []workspace.WorkspaceMember{{UserID: "usr_1", MemberID: "mem_1"}}}, nil
}

func (store *fakeWorkspaceAdminStore) GetWorkspaceMemberForUser(context.Context, string, string, string) (workspace.WorkspaceMember, error) {
	return workspace.WorkspaceMember{UserID: "usr_1", MemberID: "mem_1"}, nil
}

func (store *fakeWorkspaceAdminStore) LookupWorkspaceMembersForUser(_ context.Context, _ string, _ string, options workspace.WorkspaceMemberLookupOptions) ([]workspace.WorkspaceMember, error) {
	store.lookupOptions = options
	return []workspace.WorkspaceMember{{UserID: "usr_1", MemberID: "mem_1"}}, nil
}

func (store *fakeWorkspaceAdminStore) RequireWorkspaceMemberManagementForUser(_ context.Context, actorUserID string, workspaceID string) error {
	store.manageArgs = []string{actorUserID, workspaceID}
	return nil
}

func (store *fakeWorkspaceAdminStore) UpdateWorkspaceMemberRoleForUser(_ context.Context, actorUserID string, workspaceID string, targetUserID string, role string) error {
	store.roleArgs = []string{actorUserID, workspaceID, targetUserID, role}
	return nil
}

func (store *fakeWorkspaceAdminStore) RemoveWorkspaceMemberForUser(_ context.Context, actorUserID string, workspaceID string, targetUserID string) ([]string, error) {
	store.removeArgs = []string{actorUserID, workspaceID, targetUserID}
	return []string{"mac_removed"}, nil
}

func (store *fakeWorkspaceAdminStore) WorkspaceMemberOffboardingImpactForUser(_ context.Context, actorUserID string, workspaceID string, targetUserID string) (workspace.WorkspaceMemberOffboardingImpact, error) {
	store.impactArgs = []string{actorUserID, workspaceID, targetUserID}
	return workspace.WorkspaceMemberOffboardingImpact{MachineCount: 1, AgentCount: 2}, nil
}

func (store *fakeWorkspaceAdminStore) ListWorkspaceInvitesForUser(context.Context, string, string) ([]workspace.WorkspaceInvite, error) {
	return []workspace.WorkspaceInvite{{ID: "invite_1", InvitedEmail: "ada@example.com"}}, nil
}

func (store *fakeWorkspaceAdminStore) RevokeWorkspaceInviteForUser(context.Context, string, string, string) error {
	return nil
}

func (store *fakeWorkspaceAdminStore) LoadWorkspaceUsageForUser(context.Context, string, string) (workspace.WorkspaceUsage, error) {
	return workspace.WorkspaceUsage{MachineCount: 1, AgentCount: 2, ChannelCount: 3}, nil
}

type fakeMachineStore struct {
	created          []string
	renamed          []string
	machineAgentArgs []string
	ownOptions       app.MachineListOptions
	allOptions       app.MachineListOptions
}

func (store *fakeMachineStore) CreateMachineForWorkspace(_ context.Context, userID string, workspaceID string, name string) (app.Machine, string, error) {
	store.created = []string{userID, workspaceID, name}
	return app.Machine{
		ID:              "mach_1",
		Name:            name,
		Status:          "online",
		APIKeyPrefix:    "pk_",
		CanManage:       true,
		Hostname:        "host",
		OS:              "darwin",
		DaemonVersion:   "1.2.3",
		Runtimes:        []string{"codex"},
		RuntimeDefaults: map[string]runtime.RuntimeDefaults{"codex": {Model: "gpt-5.4", ReasoningEffort: "high"}},
		CreatedAt:       time.Unix(10, 0).UTC(),
		CreatedByUserID: userID,
	}, "api-key", nil
}

func (store *fakeMachineStore) ListOwnMachinesForWorkspace(context.Context, string, string) ([]app.Machine, error) {
	return []app.Machine{{ID: "mach_own", Runtimes: []string{"codex"}}}, nil
}

func (store *fakeMachineStore) ListOwnMachinesForWorkspacePage(_ context.Context, _ string, _ string, options app.MachineListOptions) (app.MachineListPage, error) {
	store.ownOptions = options
	return app.MachineListPage{
		Machines:   []app.Machine{{ID: "mach_own_page", Runtimes: []string{"codex"}}},
		HasMore:    true,
		NextCursor: "cursor_own",
	}, nil
}

func (store *fakeMachineStore) ListAllMachinesForWorkspace(context.Context, string, string) ([]app.Machine, error) {
	return []app.Machine{{ID: "mach_all"}}, nil
}

func (store *fakeMachineStore) ListAllMachinesForWorkspacePage(_ context.Context, _ string, _ string, options app.MachineListOptions) (app.MachineListPage, error) {
	store.allOptions = options
	return app.MachineListPage{
		Machines:   []app.Machine{{ID: "mach_all_page"}},
		NextCursor: "cursor_all",
	}, nil
}

func (store *fakeMachineStore) AccountBehaviorSettingsForUser(_ context.Context, userID string) (agent.AccountBehaviorSettings, error) {
	return agent.AccountBehaviorSettings{
		UserID:                      userID,
		VisibleReplyContractEnabled: true,
		RuntimeDebugEnabled:         false,
	}, nil
}

func (store *fakeMachineStore) UpdateAccountBehaviorSettingsForUser(_ context.Context, userID string, update agent.AccountBehaviorSettingsUpdate) (agent.AccountBehaviorSettings, error) {
	enabled := true
	if update.VisibleReplyContractEnabled != nil {
		enabled = *update.VisibleReplyContractEnabled
	}
	runtimeDebugEnabled := false
	if update.RuntimeDebugEnabled != nil {
		runtimeDebugEnabled = *update.RuntimeDebugEnabled
	}
	return agent.AccountBehaviorSettings{
		UserID:                      userID,
		VisibleReplyContractEnabled: enabled,
		RuntimeDebugEnabled:         runtimeDebugEnabled,
	}, nil
}

func (store *fakeMachineStore) RenameMachineForWorkspace(_ context.Context, userID string, workspaceID string, machineID string, name string) error {
	store.renamed = []string{userID, workspaceID, machineID, name}
	return nil
}

func (store *fakeMachineStore) DeleteMachineForWorkspace(context.Context, string, string, string) error {
	return nil
}

func (store *fakeMachineStore) EvictMachineForWorkspace(context.Context, string, string, string) ([]string, error) {
	return []string{"agent_1"}, nil
}

func (store *fakeMachineStore) ListAgentsForWorkspace(context.Context, string, string) ([]agent.AppAgent, error) {
	return []agent.AppAgent{{ID: "agent_1"}}, nil
}

func (store *fakeMachineStore) ListAgentsForMachineWorkspace(_ context.Context, userID string, workspaceID string, machineID string) ([]agent.AppAgent, error) {
	store.machineAgentArgs = []string{userID, workspaceID, machineID}
	return []agent.AppAgent{{ID: "agent_machine"}}, nil
}

type fakeMachineTokenStore struct{}

func (fakeMachineTokenStore) CreateMachineConnectToken(context.Context, string, string, string, string) (string, error) {
	return "connect-token", nil
}

func (fakeMachineTokenStore) ExchangeMachineConnectToken(context.Context, string) (MachineConnectExchangeResult, error) {
	return MachineConnectExchangeResult{
		MachineID:   "mach_1",
		APIKey:      "api_key_1",
		ServerURL:   "https://agentteam",
		WorkspaceID: "wsp_1",
	}, nil
}

func (fakeMachineTokenStore) ResolveMachineAuth(context.Context, string, string) (MachineConnectExchangeResult, error) {
	return MachineConnectExchangeResult{
		MachineID:   "mach_1",
		APIKey:      "api_key_1",
		ServerURL:   "https://agentteam",
		WorkspaceID: "wsp_1",
	}, nil
}

func (fakeMachineTokenStore) RequireMachineManagementForUser(context.Context, string, string, string) error {
	return nil
}

func (fakeMachineTokenStore) RotateMachineKeyForUser(context.Context, string, string, string) (string, error) {
	return "rotated-key", nil
}

func TestWorkspaceFlowReportsMissingDependencies(t *testing.T) {
	t.Parallel()

	var service *Service
	_, err := service.ListUserWorkspaces(context.Background(), "usr_1")
	require.ErrorContains(t, err, "workspace store is not configured")
	_, _, err = New(Dependencies{}).CreateMachineForWorkspace(context.Background(), "usr_1", "wsp_1", "Laptop")
	require.ErrorContains(t, err, "machine store is not configured")
	_, err = New(Dependencies{}).RotateMachineKeyForUser(context.Background(), "usr_1", "wsp_1", "mach_1")
	require.ErrorContains(t, err, "machine token store is not configured")
}

func TestWorkspaceFlowDelegatesWorkspaceOperations(t *testing.T) {
	t.Parallel()

	workspaces := &fakeWorkspaceStore{}
	admin := &fakeWorkspaceAdminStore{}
	service := New(Dependencies{Workspaces: workspaces, WorkspaceAdmin: admin})

	list, err := service.ListUserWorkspaces(context.Background(), "usr_1")
	require.NoError(t, err)
	require.Equal(t, []workspace.Summary{{WorkspaceID: "wsp_1", Name: "Acme"}}, list)
	require.Equal(t, "usr_1", workspaces.listUserID)

	summary, err := service.GetWorkspaceForUser(context.Background(), "usr_1", "wsp_1")
	require.NoError(t, err)
	require.Equal(t, "mem_1", summary.MemberID)
	require.Equal(t, []string{"usr_1", "wsp_1"}, workspaces.getArgs)

	updated, err := service.UpdateWorkspaceNameForUser(context.Background(), "usr_1", "wsp_1", "Renamed")
	require.NoError(t, err)
	require.Equal(t, "Renamed", updated.Name)
	require.Equal(t, []string{"usr_1", "wsp_1", "Renamed"}, workspaces.updateArgs)

	expiresAt := time.Unix(20, 0).UTC()
	inviteID, err := service.CreateWorkspaceInvite(context.Background(), "usr_1", "wsp_1", "ada@example.com", "hash", expiresAt)
	require.NoError(t, err)
	require.Equal(t, "invite_1", inviteID)
	require.Equal(t, []any{"usr_1", "wsp_1", "ada@example.com", "hash", expiresAt}, workspaces.inviteArgs)

	require.NoError(t, service.UpdateWorkspaceMemberRoleForUser(context.Background(), "actor_1", "wsp_1", "usr_2", "admin"))
	require.Equal(t, []string{"actor_1", "wsp_1", "usr_2", "admin"}, admin.roleArgs)

	require.NoError(t, service.RequireWorkspaceMemberManagementForUser(context.Background(), "actor_1", "wsp_1"))
	require.Equal(t, []string{"actor_1", "wsp_1"}, admin.manageArgs)

	removedMachines, err := service.RemoveWorkspaceMemberForUser(context.Background(), "actor_1", "wsp_1", "usr_2")
	require.NoError(t, err)
	require.Equal(t, []string{"mac_removed"}, removedMachines)
	require.Equal(t, []string{"actor_1", "wsp_1", "usr_2"}, admin.removeArgs)

	impact, err := service.WorkspaceMemberOffboardingImpactForUser(context.Background(), "actor_1", "wsp_1", "usr_2")
	require.NoError(t, err)
	require.Equal(t, workspace.WorkspaceMemberOffboardingImpact{MachineCount: 1, AgentCount: 2}, impact)
	require.Equal(t, []string{"actor_1", "wsp_1", "usr_2"}, admin.impactArgs)

}

func TestWorkspaceFlowMachineDelegationAndConversion(t *testing.T) {
	t.Parallel()

	machines := &fakeMachineStore{}
	service := New(Dependencies{Machines: machines, MachineTokens: fakeMachineTokenStore{}})

	machine, apiKey, err := service.CreateMachineForWorkspace(context.Background(), "usr_1", "wsp_1", "Laptop")
	require.NoError(t, err)
	require.Equal(t, "api-key", apiKey)
	require.Equal(t, []string{"usr_1", "wsp_1", "Laptop"}, machines.created)
	require.Equal(t, Machine{
		ID:              "mach_1",
		Name:            "Laptop",
		Status:          "online",
		APIKeyPrefix:    "pk_",
		CanManage:       true,
		Hostname:        "host",
		OS:              "darwin",
		DaemonVersion:   "1.2.3",
		Runtimes:        []string{"codex"},
		RuntimeDefaults: map[string]RuntimeDefaults{"codex": {Model: "gpt-5.4", ReasoningEffort: "high"}},
		CreatedAt:       time.Unix(10, 0).UTC(),
		CreatedByUserID: "usr_1",
	}, machine)

	machine.Runtimes[0] = "mutated"
	machine.RuntimeDefaults["codex"] = RuntimeDefaults{Model: "mutated"}
	freshMachine, _, err := service.CreateMachineForWorkspace(context.Background(), "usr_1", "wsp_1", "Laptop")
	require.NoError(t, err)
	require.Equal(t, []string{"codex"}, freshMachine.Runtimes)
	require.Equal(t, RuntimeDefaults{Model: "gpt-5.4", ReasoningEffort: "high"}, freshMachine.RuntimeDefaults["codex"])

	ownMachines, err := service.ListOwnMachinesForWorkspace(context.Background(), "usr_1", "wsp_1")
	require.NoError(t, err)
	require.Equal(t, []Machine{{ID: "mach_own", Runtimes: []string{"codex"}, RuntimeDefaults: map[string]RuntimeDefaults{}}}, ownMachines)

	allMachines, err := service.ListAllMachinesForWorkspace(context.Background(), "usr_1", "wsp_1")
	require.NoError(t, err)
	require.Equal(t, []Machine{{ID: "mach_all", Runtimes: []string{}, RuntimeDefaults: map[string]RuntimeDefaults{}}}, allMachines)
	require.NotNil(t, allMachines[0].Runtimes)
	require.NotNil(t, allMachines[0].RuntimeDefaults)

	ownMachinesPage, err := service.ListOwnMachinesForWorkspacePage(context.Background(), "usr_1", "wsp_1", MachineListOptions{Limit: 10, Cursor: "cursor"})
	require.NoError(t, err)
	require.Equal(t, app.MachineListOptions{Limit: 10, Cursor: "cursor"}, machines.ownOptions)
	require.Equal(t, MachineListPage{
		Machines:   []Machine{{ID: "mach_own_page", Runtimes: []string{"codex"}, RuntimeDefaults: map[string]RuntimeDefaults{}}},
		HasMore:    true,
		NextCursor: "cursor_own",
	}, ownMachinesPage)

	allMachinesPage, err := service.ListAllMachinesForWorkspacePage(context.Background(), "usr_1", "wsp_1", MachineListOptions{Limit: 20})
	require.NoError(t, err)
	require.Equal(t, app.MachineListOptions{Limit: 20}, machines.allOptions)
	require.Equal(t, MachineListPage{
		Machines:   []Machine{{ID: "mach_all_page", Runtimes: []string{}, RuntimeDefaults: map[string]RuntimeDefaults{}}},
		NextCursor: "cursor_all",
	}, allMachinesPage)

	require.NoError(t, service.RenameMachineForWorkspace(context.Background(), "usr_1", "wsp_1", "mach_1", "New Name"))
	require.Equal(t, []string{"usr_1", "wsp_1", "mach_1", "New Name"}, machines.renamed)

	agents, err := service.ListAgentsForWorkspace(context.Background(), "usr_1", "wsp_1")
	require.NoError(t, err)
	require.Equal(t, []agent.AppAgent{{ID: "agent_1"}}, agents)

	machineAgents, err := service.ListAgentsForMachineWorkspace(context.Background(), "usr_1", "wsp_1", "mach_1")
	require.NoError(t, err)
	require.Equal(t, []agent.AppAgent{{ID: "agent_machine"}}, machineAgents)
	require.Equal(t, []string{"usr_1", "wsp_1", "mach_1"}, machines.machineAgentArgs)

	token, err := service.CreateMachineConnectToken(context.Background(), "usr_1", "wsp_1", "mach_1", "https://agentteam")
	require.NoError(t, err)
	require.Equal(t, "connect-token", token)

	exchanged, err := service.ExchangeMachineConnectToken(context.Background(), "connect-token")
	require.NoError(t, err)
	require.Equal(t, "mach_1", exchanged.MachineID)
	require.Equal(t, "api_key_1", exchanged.APIKey)
	require.Equal(t, "https://agentteam", exchanged.ServerURL)
	require.Equal(t, "wsp_1", exchanged.WorkspaceID)

	rotated, err := service.RotateMachineKeyForUser(context.Background(), "usr_1", "wsp_1", "mach_1")
	require.NoError(t, err)
	require.Equal(t, "rotated-key", rotated)
}

func TestWorkspaceFlowPropagatesStoreErrors(t *testing.T) {
	t.Parallel()

	sentinel := errors.New("boom")
	service := New(Dependencies{Machines: failingMachineStore{err: sentinel}})
	_, _, err := service.CreateMachineForWorkspace(context.Background(), "usr_1", "wsp_1", "Laptop")
	require.ErrorIs(t, err, sentinel)
}

type failingMachineStore struct{ err error }

func (store failingMachineStore) CreateMachineForWorkspace(context.Context, string, string, string) (app.Machine, string, error) {
	return app.Machine{}, "", store.err
}

func (store failingMachineStore) ListOwnMachinesForWorkspace(context.Context, string, string) ([]app.Machine, error) {
	return nil, store.err
}

func (store failingMachineStore) ListOwnMachinesForWorkspacePage(context.Context, string, string, app.MachineListOptions) (app.MachineListPage, error) {
	return app.MachineListPage{}, store.err
}

func (store failingMachineStore) ListAllMachinesForWorkspace(context.Context, string, string) ([]app.Machine, error) {
	return nil, store.err
}

func (store failingMachineStore) ListAllMachinesForWorkspacePage(context.Context, string, string, app.MachineListOptions) (app.MachineListPage, error) {
	return app.MachineListPage{}, store.err
}

func (store failingMachineStore) AccountBehaviorSettingsForUser(context.Context, string) (agent.AccountBehaviorSettings, error) {
	return agent.AccountBehaviorSettings{}, store.err
}

func (store failingMachineStore) UpdateAccountBehaviorSettingsForUser(context.Context, string, agent.AccountBehaviorSettingsUpdate) (agent.AccountBehaviorSettings, error) {
	return agent.AccountBehaviorSettings{}, store.err
}

func (store failingMachineStore) RenameMachineForWorkspace(context.Context, string, string, string, string) error {
	return store.err
}

func (store failingMachineStore) DeleteMachineForWorkspace(context.Context, string, string, string) error {
	return store.err
}

func (store failingMachineStore) EvictMachineForWorkspace(context.Context, string, string, string) ([]string, error) {
	return nil, store.err
}

func (store failingMachineStore) ListAgentsForWorkspace(context.Context, string, string) ([]agent.AppAgent, error) {
	return nil, store.err
}

func (store failingMachineStore) ListAgentsForMachineWorkspace(context.Context, string, string, string) ([]agent.AppAgent, error) {
	return nil, store.err
}
