package util

import (
	"testing"

	policyengine "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
	nativeschema "git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
	"github.com/stretchr/testify/require"
)

func TestParseAPERule(t *testing.T) {
	tests := [...]struct {
		name       string
		rule       string
		expectErr  error
		expectRule policyengine.Rule
	}{
		{
			name: "Valid allow rule",
			rule: "allow Object.Put *",
			expectRule: policyengine.Rule{
				Status:    policyengine.Allow,
				Actions:   policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
				Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
				Condition: []policyengine.Condition{},
			},
		},
		{
			name: "Valid deny rule",
			rule: "deny Object.Put *",
			expectRule: policyengine.Rule{
				Status:    policyengine.AccessDenied,
				Actions:   policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
				Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
				Condition: []policyengine.Condition{},
			},
		},
		{
			name: "Valid deny rule with action detail",
			rule: "deny:QuotaLimitReached Object.Put *",
			expectRule: policyengine.Rule{
				Status:    policyengine.QuotaLimitReached,
				Actions:   policyengine.Actions{Names: []string{nativeschema.MethodPutObject}},
				Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
				Condition: []policyengine.Condition{},
			},
		},
		{
			name: "Valid allow rule with conditions",
			rule: "allow Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
			expectRule: policyengine.Rule{
				Status:    policyengine.Allow,
				Actions:   policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
				Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
				Condition: []policyengine.Condition{
					{
						Op:     policyengine.CondStringEquals,
						Object: policyengine.ObjectResource,
						Key:    "Department",
						Value:  "HR",
					},
					{
						Op:     policyengine.CondStringNotEquals,
						Object: policyengine.ObjectRequest,
						Key:    "Actor",
						Value:  "ownerA",
					},
				},
			},
		},
		{
			name: "Valid rule with conditions with action detail",
			rule: "deny:QuotaLimitReached Object.Get Object.Resource:Department=HR Object.Request:Actor!=ownerA *",
			expectRule: policyengine.Rule{
				Status:    policyengine.QuotaLimitReached,
				Actions:   policyengine.Actions{Names: []string{nativeschema.MethodGetObject}},
				Resources: policyengine.Resources{Names: []string{nativeschema.ResourceFormatRootObjects}},
				Condition: []policyengine.Condition{
					{
						Op:     policyengine.CondStringEquals,
						Object: policyengine.ObjectResource,
						Key:    "Department",
						Value:  "HR",
					},
					{
						Op:     policyengine.CondStringNotEquals,
						Object: policyengine.ObjectRequest,
						Key:    "Actor",
						Value:  "ownerA",
					},
				},
			},
		},
		{
			name:      "Invalid rule with unknown action",
			rule:      "permit Object.Put *",
			expectErr: errUnknownAction,
		},
		{
			name:      "Invalid rule with unknown operation",
			rule:      "allow Object.PutOut *",
			expectErr: errUnknownOperation,
		},
		{
			name:      "Invalid rule with unknown action detail",
			rule:      "deny:UnknownActionDetail Object.Put *",
			expectErr: errUnknownActionDetail,
		},
		{
			name:      "Invalid rule with unknown condition binary operator",
			rule:      "deny Object.Put Object.Resource:Department<HR *",
			expectErr: errUnknownBinaryOperator,
		},
		{
			name:      "Invalid rule with unknown condition object type",
			rule:      "deny Object.Put Object.ResourZe:Department=HR *",
			expectErr: errUnknownCondObjectType,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			r := new(policyengine.Rule)
			err := ParseAPERule(r, test.rule)
			require.ErrorIs(t, err, test.expectErr)
			if test.expectErr == nil {
				require.Equal(t, test.expectRule, *r)
			}
		})
	}
}