package installation

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

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
	"github.com/tidbcloud/AgentTeam/internal/store/app/installation"
	"github.com/tidbcloud/AgentTeam/internal/store/app/registry"
	"github.com/tidbcloud/AgentTeam/internal/store/serviceactor"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
)

// TestGrantAccessBoundsScopesAndTargets proves ACL grants cannot widen the
// installation's active scope set and must target active human members.
func TestGrantAccessBoundsScopesAndTargets(t *testing.T) {
	ctx := context.Background()
	db := startDB(t, ctx)
	svc := New(db, nil, Hooks{})
	now := time.Now().UTC()
	admin, _ := seedWorkspace(t, db, "svc_bounds", now)
	owner := seedMember(t, db, admin.WorkspaceID, "svc_bounds_owner", now)
	peer := seedMember(t, db, admin.WorkspaceID, "svc_bounds_peer", now)

	installation, err := svc.InstallForSelf(ctx, owner.UserID, admin.WorkspaceID, InstallParams{
		AppID: registry.BraveSearchAppID,
	})
	require.NoError(t, err)

	grants, err := serviceactor.New(db).ListActiveGrantScopesByInstallationIDs(ctx, []string{installation.ID})
	require.NoError(t, err)
	_ = grants[installation.ID]

	_, err = svc.GrantAccess(ctx, owner.UserID, admin.WorkspaceID, installation.ID, AccessGrantParams{
		PrincipalKind: serviceactor.PrincipalKindHuman,
		PrincipalRef:  peer.MemberID,
		AccessRole:    serviceactor.AccessRoleUse,
		Scopes:        []string{"not.an.active.scope"},
	})
	require.Error(t, err)
	require.Contains(t, err.Error(), "grant_scope_denied")

	mustExec(t, db, `UPDATE members SET status = 'inactive', updated_at = $3 WHERE workspace_id = $1 AND id = $2`, admin.WorkspaceID, peer.MemberID, now)
	_, err = svc.GrantAccess(ctx, owner.UserID, admin.WorkspaceID, installation.ID, AccessGrantParams{
		PrincipalKind: serviceactor.PrincipalKindHuman,
		PrincipalRef:  peer.MemberID,
		AccessRole:    serviceactor.AccessRoleUse,
	})
	require.Error(t, err)
	require.Contains(t, err.Error(), "active human member")

	mustExec(t, db, `INSERT INTO members (id, workspace_id, type, display_name, status, created_at, updated_at) VALUES ($1, $2, 'agent', 'Bounds Agent', 'active', $3, $3)`, "mem_svc_bounds_agent", admin.WorkspaceID, now)
	_, err = svc.GrantAccess(ctx, owner.UserID, admin.WorkspaceID, installation.ID, AccessGrantParams{
		PrincipalKind: serviceactor.PrincipalKindHuman,
		PrincipalRef:  "mem_svc_bounds_agent",
		AccessRole:    serviceactor.AccessRoleUse,
	})
	require.Error(t, err)
	require.Contains(t, err.Error(), "active human member")
}

// TestWorkspaceInstallationAdminACL proves the unified ACL surface keeps
// serving workspace (product) installations for privileged members: the
// service-installer permission admits list/grant/revoke on unattributed
// workspace installations, not only personal ones.
func TestWorkspaceInstallationAdminACL(t *testing.T) {
	ctx := context.Background()
	db := startDB(t, ctx)
	svc := New(db, nil, Hooks{})
	now := time.Now().UTC()
	admin, _ := seedWorkspace(t, db, "svc_ws_acl", now)
	member := seedMember(t, db, admin.WorkspaceID, "svc_ws_acl_member", now)

	// Install a product App as the workspace default installation through
	// the canonical installer.
	var installationID string
	err := coredb.WithTx(ctx, db, func(tx coredb.Tx) error {
		app, err := installation.LoadWorkspaceInstallableApp(ctx, tx, admin.WorkspaceID, registry.BraveSearchAppID, "")
		if err != nil {
			return err
		}
		version, err := installation.LoadInstallableAppVersion(ctx, tx, app.ID, "", app.LatestVersion)
		if err != nil {
			return err
		}
		configJSON, err := installation.MarshalJSONObject(map[string]any{}, "config")
		if err != nil {
			return err
		}
		if err := installation.LockAppInstallationMutationScope(ctx, tx, app.ID); err != nil {
			return err
		}
		txStore := serviceactor.New(db).InTx(tx)
		installationID, err = installation.InstallPreparedInstance(
			ctx, tx, txStore, admin.WorkspaceID, admin.MemberID,
			installation.PreparedInstance{
				DefinitionID:  app.ID,
				Version:       version.Version,
				VersionScopes: version.Scopes,
				ConfigJSON:    configJSON,
			},
			installation.InstanceIdentity{
				InstanceKey: "default",
				Handle:      app.Slug,
				DisplayName: app.Name,
			},
			installation.InstallCallbacks{},
			now,
		)
		return err
	})
	require.NoError(t, err)

	// The workspace admin lists grants and grants access on the workspace
	// installation through the unified surface.
	accessRows, err := svc.ListAccess(ctx, admin.UserID, admin.WorkspaceID, installationID)
	require.NoError(t, err)
	_, err = svc.GrantAccess(ctx, admin.UserID, admin.WorkspaceID, installationID, AccessGrantParams{
		PrincipalKind: serviceactor.PrincipalKindHuman,
		PrincipalRef:  member.MemberID,
		AccessRole:    serviceactor.AccessRoleUse,
	})
	require.NoError(t, err)

	// Workspace installations reject ACL manage grants outright: their
	// management stays on the legacy authority path.
	_, err = svc.GrantAccess(ctx, admin.UserID, admin.WorkspaceID, installationID, AccessGrantParams{
		PrincipalKind: serviceactor.PrincipalKindHuman,
		PrincipalRef:  member.MemberID,
		AccessRole:    serviceactor.AccessRoleManage,
	})
	require.Error(t, err)
	require.Contains(t, err.Error(), "manage grants require a personal installation")
	_, _, adminManageErr := svc.requireManagement(ctx, admin.UserID, admin.WorkspaceID, installationID)
	require.NoError(t, adminManageErr, "workspace admin must resolve CanManage=true")

	// An ordinary member cannot manage the workspace installation; the
	// rejection carries the store error contract, not access sentinels.
	_, _, err = svc.requireManagement(ctx, member.UserID, admin.WorkspaceID, installationID)
	require.Error(t, err)
	require.True(t,
		errors.Is(err, storeerr.ErrForbidden) || errors.Is(err, storeerr.ErrNotFound),
		"rejection must use the store error contract, got: %v", err,
	)
	_ = accessRows
}
