package hello

import (
	"context"
	"fmt"
	"github.com/tidbcloud/tiinf/sdk/rbac"
	"log"
	"net"
	"net/http"

	"github.com/gin-gonic/gin"

	grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware"
	grpc_auth "github.com/grpc-ecosystem/go-grpc-middleware/auth"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	"github.com/tidbcloud/tiinf/sdk/rbac/common"
	"github.com/tidbcloud/tiinf/sdk/rbac/helperimpl"
	"github.com/tidbcloud/tiinf/sdk/rbac/useadapter"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/metadata"
	"google.golang.org/grpc/reflection"
)

// Example_Use_GrpcGatewayPermissionCheckInterceptor example for GrpcGatewayPermissionCheckInterceptor
func Example_Use_GrpcGatewayPermissionCheckInterceptor() {
	listen, _ := net.Listen("tcp", ":8081")
	server := grpc.NewServer(
		grpc.UnaryInterceptor(
			grpc_middleware.ChainUnaryServer(
				// add extract user interceptor
				grpc_auth.UnaryServerInterceptor(extractIdentityAuthFunc),
				// add permission check interceptor
				useadapter.GrpcGatewayPermissionCheckUnaryServerInterceptorWithCommonParam(&rbac.PermissionCheckerCommonParam{
					IdentityF:               helperimpl.NewContextStoreGetIdentityFunc(ctxIdentityKey),
					PathPattern2ResourceMap: pathPattern2ResourceMap,
					PathWhitelist:           pathWhitelist,
					ResourceCascadeF:        checkCascadeResource,
				}),
			),
		),
	)
	RegisterGreeterServer(server, &helloService{})
	reflection.Register(server)
	go func() {
		err := server.Serve(listen)
		if err != nil {
			fmt.Println("grpc server serve error")
			panic(err)
		}
	}()

	mux := runtime.NewServeMux(
		// transfer http metadata to grpc context (rbac need)
		runtime.WithMetadata(useadapter.AddHttpMetadata2GrpcContext),
		// transfer user relate info
		runtime.WithMetadata(addAuthorization),
	)
	options := []grpc.DialOption{
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	}
	err := RegisterGreeterHandlerFromEndpoint(context.Background(), mux, "127.0.0.1:8081", options)
	if err != nil {
		fmt.Println("grpc gateway register handler error")
		panic(err)
	}

	httpServer := http.Server{
		Addr:    ":8082",
		Handler: mux,
	}
	go func() {
		err = httpServer.ListenAndServe()
		if err != nil {
			fmt.Println("http server serve error")
			panic(err)
		}
	}()

	e := gin.Default()
	e.Any("/*any", gin.WrapH(mux))
	e.Run(":8083")
}

type helloService struct {
	UnimplementedGreeterServer
}

func (s *helloService) SayHello(ctx context.Context, req *HelloRequest) (*HelloReply, error) {
	log.Println("### example grpc gateway ###, say hello: ", req.Name)

	reply := HelloReply{
		Message: "done",
	}
	return &reply, nil
}

func (s *helloService) TestName(ctx context.Context, req *HelloRequest) (*HelloReply, error) {
	log.Println("### example grpc gateway ###, test name: ", req.Name)

	reply := HelloReply{
		Message: "done",
	}
	return &reply, nil
}

func addAuthorization(c context.Context, r *http.Request) metadata.MD {
	header := r.Header
	hs := header["Authorization"]
	if hs == nil || len(hs) == 0 {
		panic("not found Authorization")
	}
	m := make(map[string]string)
	m["Authorization"] = hs[0]

	return metadata.New(m)
}

var ctxIdentityKey = "ctxIdentityKey"

func extractIdentityAuthFunc(ctx context.Context) (context.Context, error) {
	uri := useadapter.GetHttpMetadataFromGrpcContext(ctx, common.METADATA_PATH)
	method := useadapter.GetHttpMetadataFromGrpcContext(ctx, common.METADATA_METHOD)
	log.Println("in grpc gateway user func, uri: ", uri)
	log.Println("in grpc gateway user func, method: ", method)

	userID := useadapter.GetHttpMetadataFromGrpcContext(ctx, "Authorization")

	// biz need set userID to context with the specified key
	newCtx := context.WithValue(ctx, ctxIdentityKey, userID)

	return newCtx, nil
}

func checkCascadeResource(ctx context.Context, resourceUri string) (matched bool, msg string, err error) {
	resourceValue := common.ParseCascadeResourceValue(resourceUri, []string{"orgs", "projects", "clusters"})
	orgId := resourceValue["orgs"]
	projectId := resourceValue["projects"]
	if orgId == "1" && projectId == "2" {
		return true, "matched", nil
	}
	return false, fmt.Sprintf("orgs:%s not match projects:%s", orgId, projectId), nil
}
