package handlers

import (
	"bytes"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/seaweedfs/seaweedfs/weed/admin/dash"
	"github.com/seaweedfs/seaweedfs/weed/s3api/policy_engine"
)

func newValidatePolicyRequest(t *testing.T, document map[string]interface{}) *http.Request {
	t.Helper()
	body, err := json.Marshal(map[string]interface{}{"document": document})
	if err != nil {
		t.Fatalf("failed to marshal request body: %v", err)
	}
	req := httptest.NewRequest(http.MethodPost, "/api/object-store/policies/validate", bytes.NewReader(body))
	req.Header.Set("Content-Type", "application/json")
	return req
}

func TestValidatePolicy(t *testing.T) {
	handlers := &PolicyHandlers{adminServer: &dash.AdminServer{}}

	tests := []struct {
		name       string
		document   map[string]interface{}
		wantStatus int
	}{
		{
			name: "valid document with Resource",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-bucket/*"},
				},
			},
			wantStatus: http.StatusOK,
		},
		{
			name: "valid document with NotResource only",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Action": "s3:GetObject", "NotResource": "arn:aws:s3:::secret-bucket/*"},
				},
			},
			wantStatus: http.StatusOK,
		},
		{
			name: "missing version",
			document: map[string]interface{}{
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-bucket/*"},
				},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "no statements",
			document: map[string]interface{}{
				"Version":   "2012-10-17",
				"Statement": []map[string]interface{}{},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "invalid effect",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Maybe", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-bucket/*"},
				},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "missing action",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Resource": "arn:aws:s3:::my-bucket/*"},
				},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "neither Resource nor NotResource",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Action": "s3:GetObject"},
				},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "both Resource and NotResource",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-bucket/*", "NotResource": "arn:aws:s3:::secret-bucket/*"},
				},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "empty Resource alongside non-empty NotResource is still a conflict",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Action": "s3:GetObject", "Resource": []string{}, "NotResource": "arn:aws:s3:::secret-bucket/*"},
				},
			},
			wantStatus: http.StatusBadRequest,
		},
		{
			name: "both Principal and NotPrincipal",
			document: map[string]interface{}{
				"Version": "2012-10-17",
				"Statement": []map[string]interface{}{
					{"Effect": "Allow", "Action": "s3:GetObject", "Resource": "arn:aws:s3:::my-bucket/*", "Principal": "*", "NotPrincipal": "arn:aws:iam::123456789012:user/bob"},
				},
			},
			wantStatus: http.StatusBadRequest,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			req := newValidatePolicyRequest(t, tt.document)
			w := httptest.NewRecorder()

			handlers.ValidatePolicy(w, req)

			if w.Code != tt.wantStatus {
				t.Fatalf("expected status %d, got %d (body: %s)", tt.wantStatus, w.Code, w.Body.String())
			}
		})
	}
}

// Sanity check that policy_engine.PolicyStatement (the type ValidatePolicy
// actually decodes into) round-trips NotResource-only statements the way the
// test above assumes.
func TestValidatePolicy_NotResourceOnlyDecodes(t *testing.T) {
	raw := []byte(`{"Effect":"Allow","Action":"s3:GetObject","NotResource":"arn:aws:s3:::secret/*"}`)
	var stmt policy_engine.PolicyStatement
	if err := json.Unmarshal(raw, &stmt); err != nil {
		t.Fatalf("failed to unmarshal statement: %v", err)
	}
	if len(stmt.Resource.Strings()) != 0 {
		t.Fatalf("expected no Resource, got %v", stmt.Resource.Strings())
	}
	if len(stmt.NotResource.Strings()) != 1 {
		t.Fatalf("expected exactly one NotResource entry, got %v", stmt.NotResource.Strings())
	}
}
