package rbac

import (
	"context"
	"net/http"
	"testing"

	"github.com/Kong/go-pdk"
	"github.com/Kong/go-pdk/bridge"
	"github.com/Kong/go-pdk/bridge/bridgetest"
	"github.com/Kong/go-pdk/request"
	"github.com/onsi/gomega"
	"google.golang.org/grpc"

	"github.com/pingcap-inc/clinic/pkg/auth/session"
	"github.com/pingcap-inc/clinic/pkg/rbac"
)

func mockRequest(t *testing.T, s []bridgetest.MockStep) request.Request {
	return request.Request{PdkBridge: bridge.New(bridgetest.Mock(t, s))}
}

type mockRBACServer struct{}

func (r *mockRBACServer) Enforce(ctx context.Context, in *rbac.EnforceRequest, opts ...grpc.CallOption) (*rbac.BoolReply, error) {
	if in.User.Internal {
		return &rbac.BoolReply{
			Res: true,
		}, nil
	}

	return &rbac.BoolReply{Res: false}, nil
}

func TestWhitelist(t *testing.T) {
	g := gomega.NewWithT(t)
	whitelist := []string{"/clinic/.*"}
	whitelistRequest, err := NewWhitelistRequest(whitelist)
	g.Expect(err).To(gomega.Succeed())

	ok, err := whitelistRequest.Request("/clinic/api/v1/login/oauth/internal/info?redirect_uri=http%3A%2F%2Flocalhost%3A8181%2Fclinic%2F%3Foauth_callback%3D1", http.MethodGet)
	g.Expect(err).To(gomega.Succeed())

	g.Expect(ok).To(gomega.BeTrue())
}

func TestHandle(t *testing.T) {
	g := gomega.NewWithT(t)

	resourceMap := map[string]string{
		`get:/clinic/api/v1/orgs/(\d+)/clusters`:     "orgs/{org_id}/clusters/read",
		`get:/api/v1/orgs/(\d+)/clusters/123/events`: "orgs/{org_id}/clusters/read",
	}
	whitelist := []string{
		"/clinic/api/v1/login",
		"/portal",
	}

	checker, err := NewClinicRBACChecker(func() (rbac.CasbinClient, error) {
		return &mockRBACServer{}, nil
	}, resourceMap, whitelist, nil)

	g.Expect(err).To(gomega.Succeed())

	kong1 := &pdk.PDK{
		Request: newRequest(t, "GET", "/clinic/api/v1/orgs/11111/clusters"),
	}

	ok, err := checker.Handle(kong1, &session.CtxUser{
		UserID:   1,
		Internal: true,
	})
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeTrue())

	kong1 = &pdk.PDK{
		Request: newRequest(t, "GET", "/clinic/api/v1/orgs/11111/clusters"),
	}
	ok, err = checker.Handle(kong1, &session.CtxUser{
		UserID:   1,
		Internal: false,
	})
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeFalse())

	kong1 = &pdk.PDK{
		Request: newRequest(t, "GET", "/api/users"),
	}

	ok, err = checker.Handle(kong1, &session.CtxUser{
		UserID:   1,
		Internal: false,
	})
	g.Expect(err != nil).To(gomega.BeTrue())
	g.Expect(ok).To(gomega.BeFalse())

	kong1 = &pdk.PDK{
		Request: newRequest(t, "GET", "/clinic/api/v1/login"),
	}

	ok, err = checker.Handle(kong1, &session.CtxUser{
		UserID:   1,
		Internal: true,
	})
	g.Expect(err).To(gomega.Succeed())
	g.Expect(ok).To(gomega.BeTrue())
}

func TestHandleUserDebugWhitelistRejectsNilUser(t *testing.T) {
	g := gomega.NewWithT(t)

	checker, err := NewClinicRBACChecker(func() (rbac.CasbinClient, error) {
		return &mockRBACServer{}, nil
	}, map[string]string{
		`get:/clinic/api/v1/orgs/(\d+)/clusters`: "orgs/{org_id}/clusters/read",
	}, []string{"/data-proxy/.*"}, []string{"/data-proxy/.*"})
	g.Expect(err).To(gomega.Succeed())

	kong := &pdk.PDK{
		Request: newRequest(t, "GET", "/data-proxy/metrics/1/api/v1/query"),
	}

	ok, err := checker.Handle(kong, (*session.CtxUser)(nil))
	g.Expect(ok).To(gomega.BeFalse())
	g.Expect(err).ToNot(gomega.BeNil())
	g.Expect(err.Error()).To(gomega.Equal("invalid user"))
}

func newRequest(t *testing.T, method, path string) request.Request {
	//	q, err := bridge.WrapHeaders(map[string][]string{
	//		"ref":   {"wayback"},
	//		"trail": {"faint"},
	//	})
	//	assert.NoError(t, err)
	//
	//	h, err := bridge.WrapHeaders(map[string][]string{
	//		"Host":         {"example.com"},
	//		"X-Two-Things": {"first", "second"},
	//	})
	//	assert.NoError(t, err)
	//
	//	body := `GET / HTTP/1.1
	//Host: example.com
	//Accept: *
	//
	//this is the content`

	return mockRequest(t, []bridgetest.MockStep{
		//{"kong.request.get_scheme", nil, bridge.WrapString("https")},
		//{"kong.request.get_host", nil, bridge.WrapString("example.com")},
		//{"kong.request.get_port", nil, &kong_plugin_protocol.Int{V: 443}},
		//{"kong.request.get_forwarded_scheme", nil, bridge.WrapString("https")},
		//{"kong.request.get_forwarded_host", nil, bridge.WrapString("example.com")},
		//{"kong.request.get_forwarded_port", nil, &kong_plugin_protocol.Int{V: 443}},
		//{"kong.request.get_http_version", nil, &kong_plugin_protocol.Number{V: 2.1}},
		{Method: "kong.request.get_path", Args: nil, Ret: bridge.WrapString(path)},
		{Method: "kong.request.get_method", Args: nil, Ret: bridge.WrapString(method)},
		//{"kong.request.get_path_with_query", nil, bridge.WrapString("/login/orout?ref=wayback")},
		//{"kong.request.get_raw_query", nil, bridge.WrapString("ref=wayback&trail=faint")},
		//{"kong.request.get_query_arg", bridge.WrapString("ref"), bridge.WrapString("wayback")},
		//{"kong.request.get_query", &kong_plugin_protocol.Int{V: 30}, q},
		//{"kong.request.get_header", bridge.WrapString("Host"), bridge.WrapString("example.com")},
		//{"kong.request.get_headers", &kong_plugin_protocol.Int{V: 30}, h},
		//{"kong.request.get_raw_body", nil, bridge.WrapString(body)},
	})
}
