package content

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/require"
	store "github.com/tidbcloud/AgentTeam/internal/store/content"
)

type nativeBindingAuthorityReader struct {
	authority store.Authority
	found     bool
	err       error
}

type workspaceAccessVerifierFunc func(context.Context, string, string) (bool, error)

func (fn workspaceAccessVerifierFunc) HasWorkspaceAccess(ctx context.Context, workspaceID, subject string) (bool, error) {
	return fn(ctx, workspaceID, subject)
}

type contentAccessPolicyFunc func(context.Context, AccessRequest) error

func (fn contentAccessPolicyFunc) AuthorizeContentAccess(ctx context.Context, request AccessRequest) error {
	return fn(ctx, request)
}

func allowedWorkspaceAccess(context.Context, string, string) (bool, error) { return true, nil }

var allowExactBinding = contentAccessPolicyFunc(func(context.Context, AccessRequest) error { return nil })

func (reader nativeBindingAuthorityReader) GetAuthority(context.Context, string, string, string) (store.Authority, bool, error) {
	return reader.authority, reader.found, reader.err
}

func TestNativeBindingPolicyRequiresSwitchRegistrationAndNativeAuthority(t *testing.T) {
	request := AccessRequest{
		Principal:   AccessPrincipal{Subject: "usr_1", WorkspaceID: "wsp_1"},
		WorkspaceID: "wsp_1", Domain: "test_tree", BindingKey: "fixture_1", Generation: 3,
		Action: AccessReadMetadata,
	}
	reader := nativeBindingAuthorityReader{found: true, authority: store.Authority{State: store.AuthorityStateNativeActive, Generation: 3}}

	binding := NativeBinding{WorkspaceID: "wsp_1", Domain: "test_tree", BindingKey: "fixture_1", Policy: allowExactBinding}
	disabled, err := NewNativeBindingPolicy(false, reader, workspaceAccessVerifierFunc(allowedWorkspaceAccess), []NativeBinding{binding})
	require.NoError(t, err)
	require.ErrorIs(t, disabled.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied)

	unregistered, err := NewNativeBindingPolicy(true, reader, workspaceAccessVerifierFunc(allowedWorkspaceAccess), nil)
	require.NoError(t, err)
	require.ErrorIs(t, unregistered.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied)

	legacy, err := NewNativeBindingPolicy(true, nativeBindingAuthorityReader{}, workspaceAccessVerifierFunc(allowedWorkspaceAccess), []NativeBinding{binding})
	require.NoError(t, err)
	require.ErrorIs(t, legacy.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied)

	native, err := NewNativeBindingPolicy(true, reader, workspaceAccessVerifierFunc(allowedWorkspaceAccess), []NativeBinding{binding})
	require.NoError(t, err)
	require.NoError(t, native.AuthorizeContentAccess(context.Background(), request))

	request.Generation = 2
	require.ErrorIs(t, native.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied)
	request.Generation = 3
	request.WorkspaceID = "wsp_2"
	request.Principal.WorkspaceID = "wsp_2"
	require.ErrorIs(t, native.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied)
}

func TestNativeBindingPolicyFailsClosedOnAuthorityError(t *testing.T) {
	policy, err := NewNativeBindingPolicy(true, nativeBindingAuthorityReader{err: errors.New("database unavailable")}, workspaceAccessVerifierFunc(allowedWorkspaceAccess), []NativeBinding{{WorkspaceID: "wsp_1", Domain: "test_tree", BindingKey: "fixture_1", Policy: allowExactBinding}})
	require.NoError(t, err)
	err = policy.AuthorizeContentAccess(context.Background(), AccessRequest{
		Principal:   AccessPrincipal{Subject: "usr_1", WorkspaceID: "wsp_1"},
		WorkspaceID: "wsp_1", Domain: "test_tree", BindingKey: "fixture_1", Action: AccessReadMetadata,
	})
	require.Error(t, err)
	require.NotErrorIs(t, err, ErrAccessDenied)
}

func TestNativeBindingPolicyComposesWorkspaceAndOwningDomainAuthorization(t *testing.T) {
	request := AccessRequest{
		Principal: AccessPrincipal{Subject: "usr_1", WorkspaceID: "wsp_1"}, WorkspaceID: "wsp_1",
		Domain: "test_tree", BindingKey: "fixture_1", ContentID: "ctn_1", Generation: 3, Action: AccessPublish,
	}
	reader := nativeBindingAuthorityReader{found: true, authority: store.Authority{State: store.AuthorityStateNativeActive, Generation: 3}}
	workspaceAllowed := false
	domainAllowed := false
	policy, err := NewNativeBindingPolicy(true, reader, workspaceAccessVerifierFunc(func(_ context.Context, workspaceID, subject string) (bool, error) {
		require.Equal(t, "wsp_1", workspaceID)
		require.Equal(t, "usr_1", subject)
		return workspaceAllowed, nil
	}), []NativeBinding{{WorkspaceID: "wsp_1", Domain: "test_tree", BindingKey: "fixture_1", Policy: contentAccessPolicyFunc(func(_ context.Context, got AccessRequest) error {
		require.Equal(t, request, got)
		if !domainAllowed {
			return ErrAccessDenied
		}
		return nil
	})}})
	require.NoError(t, err)
	require.ErrorIs(t, policy.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied, "workspace access is mandatory")
	workspaceAllowed = true
	require.ErrorIs(t, policy.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied, "owning-domain ACL is mandatory")
	domainAllowed = true
	require.NoError(t, policy.AuthorizeContentAccess(context.Background(), request))

	request.Action = AccessAction("unknown")
	require.ErrorIs(t, policy.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied)
	request.Action = AccessPublish
	request.ContentID = "ctn_other"
	domainAllowed = false
	require.ErrorIs(t, policy.AuthorizeContentAccess(context.Background(), request), ErrAccessDenied)
}
