package message

import (
	"context"
	"testing"
	"time"

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

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

	ctx := context.Background()
	db := startMessagingTestPool(t, ctx)
	now := time.Date(2026, 7, 10, 8, 30, 0, 0, time.UTC)
	workspaceID := "wsp_sa_pin"
	ownerUserID := "usr_sa_pin_owner"
	ownerMemberID := "mem_sa_pin_owner"
	audienceUserID := "usr_sa_pin_audience"
	audienceMemberID := "mem_sa_pin_audience"
	serviceMemberID := "mem_sa_pin_service"
	scopeID := "sasc_sa_pin"
	messageID := "msg_sa_pin"
	installationID := "svcinst_sa_pin"
	serviceProjectionID := "svcmem_sa_pin"
	accountID := "sacct_sa_pin"

	insertMessagingWorkspace(t, ctx, db, workspaceID, now)
	insertMessagingUser(t, ctx, db, ownerUserID, "sa-pin-owner@example.com", now)
	insertMessagingHumanMember(t, ctx, db, ownerMemberID, workspaceID, ownerUserID, now)
	mustExecMessaging(t, ctx, db, `UPDATE members SET workspace_role = 'admin' WHERE workspace_id = $1 AND id = $2`, workspaceID, ownerMemberID)
	insertMessagingUser(t, ctx, db, audienceUserID, "sa-pin-audience@example.com", now)
	insertMessagingHumanMember(t, ctx, db, audienceMemberID, workspaceID, audienceUserID, now)
	insertMessagingServiceMember(t, ctx, db, serviceMemberID, workspaceID, "Service News", now)

	repos := NewScopeRepositories(db)
	require.NoError(t, repos.Scopes.UpsertScope(ctx, ScopeRecord{
		ID:                scopeID,
		WorkspaceID:       workspaceID,
		Type:              "service_account",
		IdentityKey:       "service_account:" + workspaceID + ":" + accountID,
		LatestSeq:         1,
		LatestGlobalSeq:   1,
		LatestMessageAt:   &now,
		Status:            "active",
		CreatedByMemberID: ownerMemberID,
		CreatedAt:         now,
		UpdatedAt:         now,
	}))
	for _, memberID := range []string{ownerMemberID, audienceMemberID} {
		effectiveFromSeq := int64(1)
		if memberID == audienceMemberID {
			effectiveFromSeq = 2
		}
		require.NoError(t, repos.Memberships.Upsert(ctx, ScopeMembershipRecord{
			ScopeID:           scopeID,
			MemberID:          memberID,
			Visibility:        "public",
			EffectiveFromSeq:  effectiveFromSeq,
			LastReadSeq:       0,
			LastReadGlobalSeq: 0,
			CreatedAt:         now,
			UpdatedAt:         now,
		}))
	}
	mustExecMessaging(t, ctx, db, `
INSERT INTO service_installations (
    id, workspace_id, service_definition_id, service_kind, instance_key,
    created_by_member_id, created_by_actor_type, created_by_actor_id,
    created_at, updated_at
) VALUES ($1, $2, 'svcdef_service_account', 'service_account', $3, $4, 'human', $4, $5, $5)
`, installationID, workspaceID, accountID, ownerMemberID, now)
	mustExecMessaging(t, ctx, db, `
INSERT INTO service_members (
    id, workspace_id, service_definition_id, service_installation_id,
    member_id, service_kind, handle, display_name, status, created_at, updated_at
) VALUES ($1, $2, 'svcdef_service_account', $3, $4, 'service_account', 'service-news', 'Service News', 'active', $5, $5)
`, serviceProjectionID, workspaceID, installationID, serviceMemberID, now)
	mustExecMessaging(t, ctx, db, `
INSERT INTO service_accounts (
    id, principal_id, workspace_id, management_kind,
    service_installation_id, service_member_id, author_member_id,
    message_scope_id, name, description,
    audience_policy_version, effective_audience_version,
    audience_state, version, created_by_member_id, created_at, updated_at
) VALUES (
    $1, $1, $2, 'workspace',
    $3, $4, $5,
    $6, 'Service News', '',
    1, 1,
    'ready', 1, $7, $8, $8
)
`, accountID, workspaceID, installationID, serviceProjectionID, serviceMemberID, scopeID, ownerMemberID, now)
	require.NoError(t, repos.Messages.Insert(ctx, ScopeMessageRecord{
		ID:                 messageID,
		WorkspaceID:        workspaceID,
		ScopeID:            scopeID,
		Seq:                1,
		WorkspaceGlobalSeq: 1,
		AuthorMemberID:     serviceMemberID,
		AuthorType:         "service",
		MessageType:        "text",
		Content:            "pin me",
		Metadata:           []byte(`{}`),
		CreatedAt:          now,
		UpdatedAt:          now,
	}))

	store := New(db, nil, 0, Options{})
	history, err := NewScopeAPIService(store).ListHistory(ctx, ScopeAPIHistoryRequest{
		WorkspaceID:    workspaceID,
		ScopeID:        scopeID,
		ViewerMemberID: audienceMemberID,
		ViewerType:     ScopeActorHuman,
		RequestKind:    ScopeVisibilityRequestHistory,
		Limit:          10,
	})
	require.NoError(t, err)
	require.Len(t, history.Messages, 1, "a current service-account audience member can read retained history from before membership")
	require.Equal(t, messageID, history.Messages[0].ID)

	unreadQuery, unreadArgs, err := BuildUnreadCountsForUserScopedLocalSeqQuery(workspaceID, audienceMemberID, audienceUserID)
	require.NoError(t, err)
	unreadRows, err := db.Query(ctx, unreadQuery, unreadArgs...)
	require.NoError(t, err)
	require.False(t, unreadRows.Next(), "retained history before effective_from_seq must not become unread attention")
	unreadRows.Close()

	list, err := store.ListMessagePinsForUser(ctx, audienceUserID, workspaceID, scopeID, MessagePinCursor{}, 10)
	require.NoError(t, err)
	require.Empty(t, list.Pins)

	_, err = store.PinMessageForUser(ctx, audienceUserID, workspaceID, scopeID, messageID)
	require.ErrorIs(t, err, ErrForbidden)

	pinned, err := store.PinMessageForUser(ctx, ownerUserID, workspaceID, scopeID, messageID)
	require.NoError(t, err)
	require.True(t, pinned.Changed)
	require.NotNil(t, pinned.Pin)
	require.Equal(t, ownerMemberID, pinned.Pin.PinnedByMemberID)

	list, err = store.ListMessagePinsForUser(ctx, audienceUserID, workspaceID, scopeID, MessagePinCursor{}, 10)
	require.NoError(t, err)
	require.Len(t, list.Pins, 1, "message-derived sidecars use the same retained-history authority")

	mustExecMessaging(t, ctx, db, `
UPDATE scope_memberships
SET effective_to_seq = 1, removed_at = $3, updated_at = $3
WHERE scope_id = $1 AND member_id = $2
`, scopeID, audienceMemberID, now.Add(time.Minute))

	history, err = NewScopeAPIService(store).ListHistory(ctx, ScopeAPIHistoryRequest{
		WorkspaceID:    workspaceID,
		ScopeID:        scopeID,
		ViewerMemberID: audienceMemberID,
		ViewerType:     ScopeActorHuman,
		RequestKind:    ScopeVisibilityRequestHistory,
		Limit:          10,
	})
	require.NoError(t, err)
	require.Empty(t, history.Messages, "closed audience membership must revoke retained-history authority")

	_, err = store.ListMessagePinsForUser(ctx, audienceUserID, workspaceID, scopeID, MessagePinCursor{}, 10)
	require.ErrorIs(t, err, ErrForbidden, "closed audience membership must revoke sidecar authority")
}
