package common

import (
	"fmt"
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"github.com/tidbcloud/infra-provider/regional-server/pkg/controllers/bucket/task"
	"k8s.io/utils/ptr"
)

func TestIsDataplaneInPolicy(t *testing.T) {
	// Sample statement that would be generated by makeBucketPolicyStatement
	sampleStatement := `{
		"Effect":    "Allow",
		"Principal": ["arn:sts::123456789:assumed-role/test-role/*"],
		"Action":    "oss:*",
		"Resource": [
			"acs:oss:*:*:test-bucket",
			"acs:oss:*:*:test-bucket/*"
		]
	}`

	// Different statement for non-matching tests
	differentStatement := `{
		"Effect":    "Allow",
		"Principal": ["arn:sts::987654321:assumed-role/different-role/*"],
		"Action":    "oss:GetObject",
		"Resource": [
			"acs:oss:*:*:different-bucket",
			"acs:oss:*:*:different-bucket/*"
		]
	}`

	tests := []struct {
		name           string
		policy         *string
		theStatement   string
		expectedResult bool
		expectedError  string
	}{
		{
			name:           "nil policy returns false",
			policy:         nil,
			theStatement:   sampleStatement,
			expectedResult: false,
			expectedError:  "",
		},
		{
			name:           "invalid JSON returns error",
			policy:         ptr.To(`{"invalid": json}`),
			theStatement:   sampleStatement,
			expectedResult: false,
			expectedError:  "failed to unmarshal policy:",
		},
		{
			name:           "missing Statement field returns error",
			policy:         ptr.To(`{"Version": "1"}`),
			theStatement:   sampleStatement,
			expectedResult: false,
			expectedError:  "statement field not found in policy",
		},
		{
			name:           "Statement field is not array returns error",
			policy:         ptr.To(`{"Statement": "not an array"}`),
			theStatement:   sampleStatement,
			expectedResult: false,
			expectedError:  "statement field is not an array",
		},
		{
			name:           "matching statement found returns true",
			policy:         createPolicyJSON(t, sampleStatement),
			theStatement:   sampleStatement,
			expectedResult: true,
			expectedError:  "",
		},
		{
			name:           "non-matching statement returns false",
			policy:         createPolicyJSON(t, differentStatement),
			theStatement:   sampleStatement,
			expectedResult: false,
			expectedError:  "",
		},
		{
			name: "multiple statements with matching one returns true",
			policy: createPolicyJSON(t,
				differentStatement,
				sampleStatement,
				`{
					"Effect":    "Deny",
					"Principal": ["*"],
					"Action":    "oss:DeleteObject",
					"Resource":  ["acs:oss:*:*:another-bucket/*"]
				}`),
			theStatement:   sampleStatement,
			expectedResult: true,
			expectedError:  "",
		},
		{
			name: "multiple statements without matching one returns false",
			policy: createPolicyJSON(t,
				differentStatement,
				`{
					"Effect":    "Deny",
					"Principal": ["*"],
					"Action":    "oss:DeleteObject",
					"Resource":  ["acs:oss:*:*:another-bucket/*"]
				}`,
			),
			theStatement:   sampleStatement,
			expectedResult: false,
			expectedError:  "",
		},
		{
			name: "array order ignored - matching statement returns true",
			policy: createPolicyJSON(t,
				`{
					"Effect":    "Allow",
					"Principal": ["arn:sts::123456789:assumed-role/test-role/*"],
					"Action":    "oss:*",
					"Resource": [
						"acs:oss:*:*:test-bucket/*",
						"acs:oss:*:*:test-bucket"
					]
				}`,
			),
			theStatement:   sampleStatement,
			expectedResult: true,
			expectedError:  "",
		},
		{
			name:           "empty statement array returns false",
			policy:         createPolicyJSON(t, ``),
			theStatement:   sampleStatement,
			expectedResult: false,
			expectedError:  "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result, err := isDataplaneInPolicy(tt.policy, tt.theStatement)

			if tt.expectedError != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
			} else {
				require.NoError(t, err)
			}

			assert.Equal(t, tt.expectedResult, result)
		})
	}
}

func TestGetBucketPolicyStatementStatus(t *testing.T) {
	// Sample statement that would be generated by makeBucketPolicyStatement
	sampleStatement := map[string]interface{}{
		"Effect":    "Allow",
		"Principal": []string{"arn:sts::123456789:assumed-role/test-role/*"},
		"Action":    "oss:*",
		"Resource": []string{
			"acs:oss:*:*:test-bucket",
			"acs:oss:*:*:test-bucket/*",
		},
	}

	// Different statement for non-matching tests
	differentStatement := map[string]interface{}{
		"Effect":    "Allow",
		"Principal": []string{"arn:sts::987654321:assumed-role/different-role/*"},
		"Action":    "oss:GetObject",
		"Resource": []string{
			"acs:oss:*:*:different-bucket",
			"acs:oss:*:*:different-bucket/*",
		},
	}

	tests := []struct {
		name             string
		policyInSpec     *string
		policyInStatus   *string
		theStatement     interface{}
		expectedInSpec   bool
		expectedInStatus bool
		expectedError    string
	}{
		{
			name:             "statement not in spec returns false, false",
			policyInSpec:     createPolicyJSON(t, `{"Effect":"Deny","Principal":"*","Action":"oss:*","Resource":"*"}`),
			policyInStatus:   createPolicyJSON(t, `{"Effect":"Deny","Principal":"*","Action":"oss:*","Resource":"*"}`),
			theStatement:     sampleStatement,
			expectedInSpec:   false,
			expectedInStatus: false,
			expectedError:    "",
		},
		{
			name:             "statement in spec but not in status returns true, false",
			policyInSpec:     createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			policyInStatus:   createPolicyJSON(t, `{"Effect":"Deny","Principal":"*","Action":"oss:*","Resource":"*"}`),
			theStatement:     sampleStatement,
			expectedInSpec:   true,
			expectedInStatus: false,
			expectedError:    "",
		},
		{
			name:             "statement in both spec and status returns true, true",
			policyInSpec:     createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			policyInStatus:   createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			theStatement:     sampleStatement,
			expectedInSpec:   true,
			expectedInStatus: true,
			expectedError:    "",
		},
		{
			name:             "nil policy in spec returns false, false",
			policyInSpec:     nil,
			policyInStatus:   createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			theStatement:     sampleStatement,
			expectedInSpec:   false,
			expectedInStatus: false,
			expectedError:    "",
		},
		{
			name:             "nil policy in status returns true, false",
			policyInSpec:     createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			policyInStatus:   nil,
			theStatement:     sampleStatement,
			expectedInSpec:   true,
			expectedInStatus: false,
			expectedError:    "",
		},
		{
			name:             "both policies nil returns false, false",
			policyInSpec:     nil,
			policyInStatus:   nil,
			theStatement:     sampleStatement,
			expectedInSpec:   false,
			expectedInStatus: false,
			expectedError:    "",
		},
		{
			name:             "invalid JSON in spec policy returns error",
			policyInSpec:     ptr.To(`{"invalid": json}`),
			policyInStatus:   createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			theStatement:     sampleStatement,
			expectedInSpec:   false,
			expectedInStatus: false,
			expectedError:    "failed to check if the statement is in policy spec",
		},
		{
			name:             "invalid JSON in status policy returns error",
			policyInSpec:     createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			policyInStatus:   ptr.To(`{"invalid": json}`),
			theStatement:     sampleStatement,
			expectedInSpec:   false,
			expectedInStatus: false,
			expectedError:    "failed to check if the statement is in policy status",
		},
		{
			name:             "different statement not in either policy returns false, false",
			policyInSpec:     createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			policyInStatus:   createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			theStatement:     differentStatement,
			expectedInSpec:   false,
			expectedInStatus: false,
			expectedError:    "",
		},
		{
			name:             "empty statement array in spec returns false, false",
			policyInSpec:     createPolicyJSON(t, ``),
			policyInStatus:   createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			theStatement:     sampleStatement,
			expectedInSpec:   false,
			expectedInStatus: false,
			expectedError:    "",
		},
		{
			name:             "empty statement array in status returns true, false",
			policyInSpec:     createPolicyJSON(t, `{"Effect":"Allow","Principal":["arn:sts::123456789:assumed-role/test-role/*"],"Action":"oss:*","Resource":["acs:oss:*:*:test-bucket","acs:oss:*:*:test-bucket/*"]}`),
			policyInStatus:   createPolicyJSON(t, ``),
			theStatement:     sampleStatement,
			expectedInSpec:   true,
			expectedInStatus: false,
			expectedError:    "",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			inSpec, inStatus, err := GetBucketPolicyStatementStatus(tt.policyInSpec, tt.policyInStatus, tt.theStatement)

			if tt.expectedError != "" {
				require.Error(t, err)
				assert.Contains(t, err.Error(), tt.expectedError)
			} else {
				require.NoError(t, err)
			}

			assert.Equal(t, tt.expectedInSpec, inSpec)
			assert.Equal(t, tt.expectedInStatus, inStatus)
		})
	}
}

func TestConvertStatementStatus2DataplanePermissionStatus(t *testing.T) {
	tests := []struct {
		name           string
		inSpec         bool
		inStatus       bool
		expectedStatus task.BucketDataplanePermissionStatus
	}{
		{
			name:           "both inSpec and inStatus true returns Synced",
			inSpec:         true,
			inStatus:       true,
			expectedStatus: task.BucketDataplanePermissionStatusSynced,
		},
		{
			name:           "inSpec true but inStatus false returns Accepted",
			inSpec:         true,
			inStatus:       false,
			expectedStatus: task.BucketDataplanePermissionStatusAccepted,
		},
		{
			name:           "both inSpec and inStatus false returns Pending",
			inSpec:         false,
			inStatus:       false,
			expectedStatus: task.BucketDataplanePermissionStatusPending,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			result := ConvertStatementStatus2DataplanePermissionStatus(tt.inSpec, tt.inStatus)
			assert.Equal(t, tt.expectedStatus, result)
		})
	}
}

// createPolicyJSON creates a JSON policy string with the given statements
func createPolicyJSON(t *testing.T, statements ...string) *string {
	t.Helper()
	sts := strings.Join(statements, ",")
	str := fmt.Sprintf(`{"Version":"1","Statement":[%s]}`, sts)
	return &str
}
