package access

import (
	"testing"

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

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

	target := ResolvedScopeTarget{
		WorkspaceID: "wsp_scope_target",
		Operation:   ScopeTargetOperationAttachmentUpload,
		Requested: ScopeTargetRef{
			Kind: ScopeTargetKindThread,
			Ref:  "thr_reply",
		},
		Subjects: []ScopeAuthoritySubject{{
			Kind:          ScopeAuthoritySubjectDurableAuthor,
			PrincipalType: PrincipalTypeAgent,
			PrincipalID:   "agt_private",
			MemberID:      "mem_private_agent",
		}},
		StorageScope: ScopeRef{
			ScopeID:   "thr_reply",
			ScopeType: ScopeTargetKindThread,
		},
		ParentScope: ScopeRef{
			ScopeID:   "chn_parent",
			ScopeType: ScopeTargetKindChannel,
		},
		RootMessageID:    "msg_root",
		RootMessageShort: "abc12345",
		PermissionBasis: []ScopePermissionBasis{{
			Kind: ScopePermissionBasisOwnerPrivateMembership,
			Scope: ScopeRef{
				ScopeID:   "chn_parent",
				ScopeType: ScopeTargetKindChannel,
			},
			DecisionSource: DecisionSourceChannelMembership,
			MemberID:       "mem_private_agent",
			OwnerMemberID:  "mem_owner",
		}},
	}

	require.NoError(t, target.Validate())
}

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

	target := ResolvedScopeTarget{
		WorkspaceID: "wsp_scope_target_pin",
		Operation:   ScopeTargetOperationMessagePinWrite,
		Requested: ScopeTargetRef{
			Kind: ScopeTargetKindChannel,
			Ref:  "chn_pin",
		},
		Subjects: []ScopeAuthoritySubject{{
			Kind:          ScopeAuthoritySubjectDurableAuthor,
			PrincipalType: PrincipalTypeUser,
			PrincipalID:   "usr_pin",
			MemberID:      "mem_pin",
		}},
		StorageScope: ScopeRef{
			ScopeID:   "chn_pin",
			ScopeType: ScopeTargetKindChannel,
		},
		ReadScope: ScopeRef{
			ScopeID:   "chn_pin",
			ScopeType: ScopeTargetKindChannel,
		},
		PermissionBasis: []ScopePermissionBasis{{
			Kind: ScopePermissionBasisDirectMembership,
			Scope: ScopeRef{
				ScopeID:   "chn_pin",
				ScopeType: ScopeTargetKindChannel,
			},
			DecisionSource: DecisionSourceChannelMembership,
			MemberID:       "mem_pin",
		}},
	}

	require.NoError(t, target.Validate())
}

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

	target := ResolvedScopeTarget{
		WorkspaceID: "wsp_service_account",
		Operation:   ScopeTargetOperationMessageSend,
		Requested: ScopeTargetRef{
			Kind: ScopeTargetKindServiceAccount,
			Ref:  "svc_scope_1",
		},
		Subjects: []ScopeAuthoritySubject{
			{Kind: ScopeAuthoritySubjectDurableAuthor, PrincipalType: PrincipalTypeService, PrincipalID: "sac_1", MemberID: "mem_service_1"},
			{Kind: ScopeAuthoritySubjectExecutionOwner, PrincipalType: PrincipalTypeUser, PrincipalID: "usr_publisher", MemberID: "mem_publisher"},
		},
		StorageScope:  ScopeRef{ScopeID: "svc_scope_1", ScopeType: ScopeTargetKindServiceAccount},
		DeliveryScope: ScopeRef{ScopeID: "svc_scope_1", ScopeType: ScopeTargetKindServiceAccount},
		ReadScope:     ScopeRef{ScopeID: "svc_scope_1", ScopeType: ScopeTargetKindServiceAccount},
		PermissionBasis: []ScopePermissionBasis{{
			Kind:           ScopePermissionBasisServiceGrant,
			Scope:          ScopeRef{ScopeID: "svc_scope_1", ScopeType: ScopeTargetKindServiceAccount},
			DecisionSource: DecisionSourceServiceGrant,
			MemberID:       "mem_publisher",
			GrantID:        "sgr_service_account_1",
		}},
	}

	require.NoError(t, target.Validate())
}

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

	target := ResolvedScopeTarget{
		WorkspaceID: " wsp_scope_target ",
		Operation:   " attachment_read ",
		Requested: ScopeTargetRef{
			Kind: " thread ",
			Ref:  " thr_reply ",
		},
		Subjects: []ScopeAuthoritySubject{{
			Kind:          " durable_author ",
			PrincipalType: " agent ",
			PrincipalID:   " agt_private ",
			MemberID:      " mem_private_agent ",
		}},
		ReadScope: ScopeRef{
			ScopeID:   " thr_reply ",
			ScopeType: " thread ",
		},
		PermissionBasis: []ScopePermissionBasis{{
			Kind:           " direct_membership ",
			Scope:          ScopeRef{ScopeID: " thr_reply ", ScopeType: " thread "},
			DecisionSource: " thread_inherit ",
			MemberID:       " mem_private_agent ",
		}},
	}

	normalized := target.Normalized()
	require.Equal(t, "wsp_scope_target", normalized.WorkspaceID)
	require.Equal(t, ScopeTargetOperationAttachmentRead, normalized.Operation)
	require.Equal(t, ScopeTargetKindThread, normalized.Requested.Kind)
	require.Equal(t, "thr_reply", normalized.Requested.Ref)
	require.Equal(t, ScopeTargetKindThread, normalized.ReadScope.ScopeType)
	require.Equal(t, "thr_reply", normalized.ReadScope.ScopeID)
	require.Equal(t, ScopeAuthoritySubjectDurableAuthor, normalized.Subjects[0].Kind)
	require.Equal(t, PrincipalTypeAgent, normalized.Subjects[0].PrincipalType)
	require.Equal(t, "agt_private", normalized.Subjects[0].PrincipalID)
	require.Equal(t, ScopePermissionBasisDirectMembership, normalized.PermissionBasis[0].Kind)
	require.Equal(t, DecisionSourceThreadInherit, normalized.PermissionBasis[0].DecisionSource)
	require.NoError(t, normalized.Validate())
}

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

	baseTarget := func() ResolvedScopeTarget {
		return ResolvedScopeTarget{
			WorkspaceID: "wsp_scope_target",
			Operation:   ScopeTargetOperationMessageRead,
			Requested: ScopeTargetRef{
				Kind: ScopeTargetKindChannel,
				Ref:  "chn_general",
			},
			Subjects: []ScopeAuthoritySubject{{
				Kind:     ScopeAuthoritySubjectDurableAuthor,
				MemberID: "mem_author",
			}},
			ReadScope: ScopeRef{
				ScopeID:   "chn_general",
				ScopeType: ScopeTargetKindChannel,
			},
			PermissionBasis: []ScopePermissionBasis{{
				Kind: ScopePermissionBasisDirectMembership,
				Scope: ScopeRef{
					ScopeID:   "chn_general",
					ScopeType: ScopeTargetKindChannel,
				},
			}},
		}
	}

	t.Run("missing role scope", func(t *testing.T) {
		candidate := baseTarget()
		candidate.ReadScope = ScopeRef{}
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})

	t.Run("root message without parent scope", func(t *testing.T) {
		candidate := baseTarget()
		candidate.RootMessageID = "msg_root"
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})

	t.Run("missing authority subject", func(t *testing.T) {
		candidate := baseTarget()
		candidate.Subjects = nil
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})

	t.Run("missing permission basis", func(t *testing.T) {
		candidate := baseTarget()
		candidate.PermissionBasis = nil
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})

	t.Run("invalid requested kind", func(t *testing.T) {
		candidate := baseTarget()
		candidate.Requested.Kind = "space"
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})

	t.Run("invalid operation", func(t *testing.T) {
		candidate := baseTarget()
		candidate.Operation = "legacy_space_write"
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})

	t.Run("invalid authority subject kind", func(t *testing.T) {
		candidate := baseTarget()
		candidate.Subjects[0].Kind = "sender"
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})

	t.Run("invalid permission basis kind", func(t *testing.T) {
		candidate := baseTarget()
		candidate.PermissionBasis[0].Kind = "fallback"
		require.ErrorIs(t, candidate.Validate(), ErrInvalidScopeTarget)
	})
}
