package access

import (
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/domain"
	"github.com/tidbcloud/AgentTeam/internal/store/model"
)

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

	membership := HumanWorkspaceMember{
		Member:        model.Member{ID: "mem_workspace"},
		WorkspaceRole: string(domain.WorkspaceRoleMember),
	}
	request := NewHumanAuthorizationRequest(
		"usr_workspace",
		"wsp_workspace",
		membership.Member.ID,
		"wsp_workspace",
		HumanAction{Resource: ResourceWorkspace, Action: ActionAccess},
	)

	decision, err := DecideHumanWorkspaceRoleAuthorization(membership, request)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
	require.Equal(t, "usr_workspace", decision.ResolvedPrincipalID)
	require.Equal(t, membership.Member.ID, decision.ResolvedMemberID)
	require.Empty(t, decision.DenyCode)
}

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

	membership := HumanWorkspaceMember{
		Member:        model.Member{ID: "mem_workspace_denied"},
		WorkspaceRole: string(domain.WorkspaceRoleMember),
	}

	t.Run("forbidden action uses workspace access denied", func(t *testing.T) {
		request := NewHumanAuthorizationRequest(
			"usr_workspace_denied",
			"wsp_workspace_denied",
			membership.Member.ID,
			"wsp_workspace_denied",
			HumanAction{Resource: ResourceWorkspace, Action: ActionManageRuntime},
		)

		decision, err := DecideHumanWorkspaceRoleAuthorization(membership, request)
		require.ErrorIs(t, err, ErrForbidden)
		require.False(t, decision.Allowed)
		require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
		require.Equal(t, DenyCodeWorkspaceAccessDenied, decision.DenyCode)
	})

	t.Run("owner only member role action still uses workspace access denied", func(t *testing.T) {
		adminMembership := HumanWorkspaceMember{
			Member:        model.Member{ID: "mem_workspace_admin_denied"},
			WorkspaceRole: string(domain.WorkspaceRoleAdmin),
		}
		request := NewHumanAuthorizationRequest(
			"usr_workspace_admin_denied",
			"wsp_workspace_denied",
			adminMembership.Member.ID,
			"wsp_workspace_denied",
			HumanAction{Resource: ResourceWorkspace, Action: ActionManageMemberRoles},
		)

		decision, err := DecideHumanWorkspaceRoleAuthorization(adminMembership, request)
		require.ErrorIs(t, err, ErrForbidden)
		require.False(t, decision.Allowed)
		require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
		require.Equal(t, DenyCodeWorkspaceAccessDenied, decision.DenyCode)
	})

	t.Run("service install action still uses workspace role source", func(t *testing.T) {
		request := NewHumanAuthorizationRequest(
			"usr_workspace_denied",
			"wsp_workspace_denied",
			membership.Member.ID,
			"wsp_workspace_denied",
			HumanAction{Resource: ResourceWorkspace, Action: ActionInstallService},
		)

		decision, err := DecideHumanWorkspaceRoleAuthorization(membership, request)
		require.ErrorIs(t, err, ErrForbidden)
		require.False(t, decision.Allowed)
		require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
		require.Equal(t, DenyCodeWorkspaceAccessDenied, decision.DenyCode)
	})

	t.Run("mismatched act as member is forbidden", func(t *testing.T) {
		request := NewHumanAuthorizationRequest(
			"usr_workspace_denied",
			"wsp_workspace_denied",
			membership.Member.ID,
			"wsp_workspace_denied",
			HumanAction{Resource: ResourceWorkspace, Action: ActionAccess},
		)
		request.ActAsMemberID = "mem_other"

		decision, err := DecideHumanWorkspaceRoleAuthorization(membership, request)
		require.ErrorIs(t, err, ErrForbidden)
		require.False(t, decision.Allowed)
		require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
		require.Equal(t, DenyCodeActAsForbidden, decision.DenyCode)
	})
}

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

	membership := HumanWorkspaceMember{
		Member:        model.Member{ID: "mem_workspace_service"},
		WorkspaceRole: string(domain.WorkspaceRoleAdmin),
	}
	request := NewHumanAuthorizationRequest(
		"usr_workspace_service",
		"wsp_workspace_service",
		membership.Member.ID,
		"svcinst_workspace_service",
		HumanAction{Resource: ResourceServiceInstallation, Action: ActionApprove},
	)

	decision, err := DecideHumanWorkspaceRoleAuthorization(membership, request)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
	require.Equal(t, "usr_workspace_service", decision.ResolvedPrincipalID)
	require.Equal(t, membership.Member.ID, decision.ResolvedMemberID)
	require.Empty(t, decision.DenyCode)
}

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

	joinedAccess := HumanChannelAccess{
		WorkspaceMember: HumanWorkspaceMember{
			Member:        model.Member{ID: "mem_channel_joined"},
			WorkspaceRole: string(domain.WorkspaceRoleMember),
		},
		Channel:   Channel{ChannelID: "chn_joined", Type: "channel"},
		Joined:    true,
		CanInvite: true,
	}
	joinedRequest := NewHumanAuthorizationRequest(
		"usr_channel_joined",
		"wsp_channel",
		joinedAccess.WorkspaceMember.Member.ID,
		joinedAccess.Channel.ChannelID,
		HumanAction{Resource: ResourceChannel, Action: ActionRead},
	)

	decision, err := DecideHumanChannelAuthorization(joinedAccess, joinedRequest)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceChannelMembership, decision.DecisionSource)
	require.Equal(t, joinedAccess.WorkspaceMember.Member.ID, decision.ResolvedMemberID)

	ownerAccess := HumanChannelAccess{
		WorkspaceMember: HumanWorkspaceMember{
			Member:        model.Member{ID: "mem_channel_owner"},
			WorkspaceRole: string(domain.WorkspaceRoleMember),
		},
		Channel:   Channel{ChannelID: "chn_owner", Type: "channel"},
		Joined:    true,
		CanInvite: true,
		IsOwner:   true,
	}
	ownerRequest := NewHumanAuthorizationRequest(
		"usr_channel_owner",
		"wsp_channel",
		ownerAccess.WorkspaceMember.Member.ID,
		ownerAccess.Channel.ChannelID,
		HumanAction{Resource: ResourceChannel, Action: ActionManageMembers},
	)

	decision, err = DecideHumanChannelAuthorization(ownerAccess, ownerRequest)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceChannelMembership, decision.DecisionSource)
	require.Equal(t, ownerAccess.WorkspaceMember.Member.ID, decision.ResolvedMemberID)
}

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

	channelAccess := HumanChannelAccess{
		WorkspaceMember: HumanWorkspaceMember{
			Member:        model.Member{ID: "mem_channel_denied"},
			WorkspaceRole: string(domain.WorkspaceRoleAdmin),
		},
		Channel: Channel{ChannelID: "chn_denied", Type: "channel"},
		Joined:  false,
	}
	request := NewHumanAuthorizationRequest(
		"usr_channel_denied",
		"wsp_channel_denied",
		channelAccess.WorkspaceMember.Member.ID,
		channelAccess.Channel.ChannelID,
		HumanAction{Resource: ResourceChannel, Action: ActionRead},
	)

	decision, err := DecideHumanChannelAuthorization(channelAccess, request)
	require.ErrorIs(t, err, ErrForbidden)
	require.False(t, decision.Allowed)
	require.Equal(t, DecisionSourceChannelMembership, decision.DecisionSource)
	require.Equal(t, DenyCodeNotChannelMember, decision.DenyCode)
}

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

	allowedAccess := HumanDMAccess{
		WorkspaceMember: HumanWorkspaceMember{Member: model.Member{ID: "mem_dm"}},
		Participant:     true,
	}
	allowedRequest := NewHumanAuthorizationRequest(
		"usr_dm",
		"wsp_dm",
		allowedAccess.WorkspaceMember.Member.ID,
		"dm_thread",
		HumanAction{Resource: ResourceDM, Action: ActionPost},
	)

	decision, err := DecideHumanDMAuthorization(allowedAccess, allowedRequest)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceDMParticipant, decision.DecisionSource)

	deniedAccess := HumanDMAccess{
		WorkspaceMember: HumanWorkspaceMember{Member: model.Member{ID: "mem_dm_denied"}},
		Participant:     false,
	}
	deniedRequest := NewHumanAuthorizationRequest(
		"usr_dm_denied",
		"wsp_dm",
		deniedAccess.WorkspaceMember.Member.ID,
		"dm_thread",
		HumanAction{Resource: ResourceDM, Action: ActionRead},
	)

	decision, err = DecideHumanDMAuthorization(deniedAccess, deniedRequest)
	require.ErrorIs(t, err, ErrForbidden)
	require.False(t, decision.Allowed)
	require.Equal(t, DenyCodeNotDMParticipant, decision.DenyCode)
}

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

	allowedAccess := HumanThreadAccess{
		WorkspaceMember:     HumanWorkspaceMember{Member: model.Member{ID: "mem_thread"}},
		ParentThreadType:    domain.ThreadTypeChannel,
		ParentChannelJoined: true,
	}
	allowedRequest := NewHumanAuthorizationRequest(
		"usr_thread",
		"wsp_thread",
		allowedAccess.WorkspaceMember.Member.ID,
		"chn_thread",
		HumanAction{Resource: ResourceThread, Action: ActionRead},
	)

	decision, err := DecideHumanThreadAuthorization(allowedAccess, allowedRequest)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceThreadInherit, decision.DecisionSource)

	deniedAccess := HumanThreadAccess{
		WorkspaceMember:     HumanWorkspaceMember{Member: model.Member{ID: "mem_thread_denied"}},
		ParentThreadType:    domain.ThreadTypeDM,
		ParentDMParticipant: false,
	}
	deniedRequest := NewHumanAuthorizationRequest(
		"usr_thread_denied",
		"wsp_thread",
		deniedAccess.WorkspaceMember.Member.ID,
		"chn_thread",
		HumanAction{Resource: ResourceThread, Action: ActionPost},
	)

	decision, err = DecideHumanThreadAuthorization(deniedAccess, deniedRequest)
	require.ErrorIs(t, err, ErrForbidden)
	require.False(t, decision.Allowed)
	require.Equal(t, DenyCodeThreadAccessDenied, decision.DenyCode)
}

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

	membership := HumanWorkspaceMember{
		Member:        model.Member{ID: "mem_invalid"},
		WorkspaceRole: string(domain.WorkspaceRoleOwner),
	}
	request := NewHumanAuthorizationRequest(
		"",
		"wsp_invalid",
		membership.Member.ID,
		"wsp_invalid",
		HumanAction{Resource: ResourceWorkspace, Action: ActionAccess},
	)

	_, err := DecideHumanWorkspaceRoleAuthorization(membership, request)
	require.ErrorIs(t, err, ErrInvalidAuthorizationRequest)
}

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

	machineAccess := HumanMachineAccess{
		WorkspaceMember: HumanWorkspaceMember{
			Member:        model.Member{ID: "mem_machine"},
			WorkspaceRole: string(domain.WorkspaceRoleAdmin),
		},
		Machine: Machine{MachineID: "mac_test"},
	}
	request := NewHumanAuthorizationRequest(
		"usr_machine",
		"wsp_machine",
		machineAccess.WorkspaceMember.Member.ID,
		machineAccess.Machine.MachineID,
		HumanAction{Resource: ResourceMachine, Action: ActionManage},
	)

	decision, err := DecideHumanMachineAuthorization(machineAccess, request)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
	require.Equal(t, machineAccess.WorkspaceMember.Member.ID, decision.ResolvedMemberID)
}

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

	agentAccess := HumanAgentAccess{
		WorkspaceMember: HumanWorkspaceMember{
			Member:        model.Member{ID: "mem_agent"},
			WorkspaceRole: string(domain.WorkspaceRoleAdmin),
		},
		Agent: Agent{AgentID: "agt_test", MachineID: "mac_test", MemberID: "mem_agent_self"},
	}
	request := NewHumanAuthorizationRequest(
		"usr_agent",
		"wsp_agent",
		agentAccess.WorkspaceMember.Member.ID,
		agentAccess.Agent.AgentID,
		HumanAction{Resource: ResourceAgent, Action: ActionReadWorkspace},
	)

	decision, err := DecideHumanAgentAuthorization(agentAccess, request)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceWorkspaceRole, decision.DecisionSource)
	require.Equal(t, agentAccess.WorkspaceMember.Member.ID, decision.ResolvedMemberID)
}

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

	agentAccess := HumanAgentAccess{
		WorkspaceMember: HumanWorkspaceMember{
			Member:        model.Member{ID: "mem_agent"},
			WorkspaceRole: string(domain.WorkspaceRoleOwner),
		},
		Agent: Agent{AgentID: "agt_test", MachineID: "mac_test", MemberID: "mem_agent_self"},
	}
	request := NewHumanAuthorizationRequest(
		"usr_agent",
		"wsp_agent",
		agentAccess.WorkspaceMember.Member.ID,
		agentAccess.Agent.AgentID,
		HumanAction{Resource: ResourceAgent, Action: ActionActAsMember},
	)

	_, err := DecideHumanAgentAuthorization(agentAccess, request)
	require.ErrorIs(t, err, ErrInvalidAuthorizationRequest)
}

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

	agent := AgentSelf{
		AgentID:     "agt_self",
		WorkspaceID: "wsp_self",
		MemberID:    "mem_self",
	}
	request := NewAgentSelfAuthorizationRequest(agent.AgentID, agent.WorkspaceID, agent.MemberID)
	request.ActAsMemberID = "mem_other"

	decision, err := DecideAgentSelfAuthorization(agent, request)
	require.ErrorIs(t, err, ErrForbidden)
	require.False(t, decision.Allowed)
	require.Equal(t, DecisionSourceAgentSelf, decision.DecisionSource)
	require.Equal(t, DenyCodeActAsForbidden, decision.DenyCode)
}

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

	machine := MachineSelf{
		MachineID:   "mac_self",
		WorkspaceID: "wsp_self",
	}
	request := NewMachineSelfAuthorizationRequest(machine.MachineID, machine.WorkspaceID)

	decision, err := DecideMachineSelfAuthorization(machine, request)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceMachineSelf, decision.DecisionSource)
	require.Equal(t, machine.MachineID, decision.ResolvedPrincipalID)
	require.Empty(t, decision.ResolvedMemberID)
}

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

	admin := SystemAdminPrincipal{UserID: "usr_system_admin"}
	request := NewSystemAdminWorkspaceAccessRequest(admin.UserID, "wsp_break_glass")

	decision, err := DecideSystemAdminWorkspaceAccess(admin, request)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceSystemAdmin, decision.DecisionSource)
	require.Equal(t, admin.UserID, decision.ResolvedPrincipalID)
	require.Empty(t, decision.ResolvedMemberID)
	require.Empty(t, decision.DenyCode)
}

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

	admin := SystemAdminPrincipal{UserID: "usr_system_admin"}
	request := NewSystemAdminWorkspaceAccessRequest(admin.UserID, "wsp_break_glass")
	request.ActAsMemberID = "mem_workspace_member"

	decision, err := DecideSystemAdminWorkspaceAccess(admin, request)
	require.ErrorIs(t, err, ErrForbidden)
	require.False(t, decision.Allowed)
	require.Equal(t, DecisionSourceSystemAdmin, decision.DecisionSource)
	require.Equal(t, DenyCodeActAsForbidden, decision.DenyCode)
	require.Empty(t, decision.ResolvedMemberID)
}

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

	admin := SystemAdminPrincipal{UserID: "usr_system_admin"}
	request := NewSystemAdminWorkspaceAccessRequest(admin.UserID, "wsp_break_glass")
	request.Action = ActionManageRuntime

	_, err := DecideSystemAdminWorkspaceAccess(admin, request)
	require.ErrorIs(t, err, ErrInvalidAuthorizationRequest)
}

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

	grant := ServiceInstallationGrant{
		PrincipalID:    "svc_taskboard",
		WorkspaceID:    "wsp_service",
		InstallationID: "svi_taskboard",
		MemberID:       "mem_service",
		Scopes:         []string{"tasks.read", "tasks.write"},
	}
	request := NewServiceInstallationInvokeRequest(
		grant.PrincipalID,
		grant.WorkspaceID,
		grant.InstallationID,
		grant.MemberID,
		[]string{"tasks.write", "tasks.read"},
	)

	decision, err := DecideServiceInstallationAuthorization(grant, request)
	require.NoError(t, err)
	require.True(t, decision.Allowed)
	require.Equal(t, DecisionSourceServiceGrant, decision.DecisionSource)
	require.Equal(t, grant.PrincipalID, decision.ResolvedPrincipalID)
	require.Equal(t, grant.MemberID, decision.ResolvedMemberID)
	require.Equal(t, []string{"tasks.read", "tasks.write"}, decision.Scopes)
	require.Empty(t, decision.DenyCode)
}

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

	grant := ServiceInstallationGrant{
		PrincipalID:    "svc_taskboard",
		WorkspaceID:    "wsp_service",
		InstallationID: "svi_taskboard",
		MemberID:       "mem_service",
		Scopes:         []string{"tasks.read"},
	}
	request := NewServiceInstallationInvokeRequest(
		grant.PrincipalID,
		grant.WorkspaceID,
		grant.InstallationID,
		grant.MemberID,
		[]string{"tasks.write"},
	)

	decision, err := DecideServiceInstallationAuthorization(grant, request)
	require.ErrorIs(t, err, ErrForbidden)
	require.False(t, decision.Allowed)
	require.Equal(t, DecisionSourceServiceGrant, decision.DecisionSource)
	require.Equal(t, DenyCodeScopeMissing, decision.DenyCode)
	require.Equal(t, grant.MemberID, decision.ResolvedMemberID)
}

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

	grant := ServiceInstallationGrant{
		PrincipalID:    "svc_taskboard",
		WorkspaceID:    "wsp_service",
		InstallationID: "svi_taskboard",
		MemberID:       "mem_service",
		Scopes:         []string{"tasks.read"},
	}
	request := NewServiceInstallationInvokeRequest(
		grant.PrincipalID,
		grant.WorkspaceID,
		grant.InstallationID,
		grant.MemberID,
		[]string{"tasks.read"},
	)
	request.ActAsMemberID = "mem_other"

	decision, err := DecideServiceInstallationAuthorization(grant, request)
	require.ErrorIs(t, err, ErrForbidden)
	require.False(t, decision.Allowed)
	require.Equal(t, DecisionSourceServiceGrant, decision.DecisionSource)
	require.Equal(t, DenyCodeActAsForbidden, decision.DenyCode)
	require.Equal(t, grant.MemberID, decision.ResolvedMemberID)
}

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

	grant := ServiceInstallationGrant{
		PrincipalID:    "svc_taskboard",
		WorkspaceID:    "wsp_service",
		InstallationID: "svi_taskboard",
		Scopes:         []string{"tasks.read"},
	}
	request := NewServiceInstallationInvokeRequest(
		grant.PrincipalID,
		grant.WorkspaceID,
		grant.InstallationID,
		"",
		nil,
	)

	_, err := DecideServiceInstallationAuthorization(grant, request)
	require.ErrorIs(t, err, ErrInvalidAuthorizationRequest)
}
