package app

import (
	"context"
	"fmt"
	"net/http"
	"net/textproto"
	"strconv"

	"github.com/gin-gonic/gin"
	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/cluster-service-ng/v1beta"
	"github.com/tidbcloud/cluster-service-ng/config"
	"github.com/tidbcloud/tiinf/tiinf"
	tigrpcclient "github.com/tidbcloud/tiinf/transport/tigrpc/client"
	"google.golang.org/grpc"
	healthv1 "google.golang.org/grpc/health/grpc_health_v1"
	"google.golang.org/protobuf/encoding/protojson"
	"google.golang.org/protobuf/proto"
)

const (
	// internal API paths
	baseAPIPath = "/api/v1beta"

	// http proxy API paths
	httpRouteAPIPath = "/cluster-ng-server/api/v1beta"

	// regional API paths
	regionalPath = "/v1"

	responseKeyStatusCode = "x-http-code"
)

func RegisterGateway(cfg *config.Config, inf tiinf.TiInf) error {
	mux, conn, err := setupHTTPGatewayOnGin(cfg, inf)
	if err != nil {
		return fmt.Errorf("init grpc-gateway failed. %w", err)
	}

	if err := apiv1.RegisterSharedPoolServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}
	if err := apiv1.RegisterNodeInstanceGroupServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}

	if err := apiv1.RegisterTiDBClusterServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}

	if err := apiv1.RegisterRuConfigServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}

	if err := apiv1.RegisterRestoreServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}

	if err := apiv1.RegisterExportServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}
	if err := apiv1.RegisterTemplateServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}

	if err := apiv1.RegisterAutoscalePolicyServiceHandler(context.Background(), mux, conn); err != nil {
		return err
	}

	return nil
}

func setupHTTPGatewayOnGin(cfg *config.Config, inf tiinf.TiInf) (*runtime.ServeMux, *grpc.ClientConn, error) {
	address := fmt.Sprintf("%s:%d", "localhost", cfg.TiInfConfig.GrpcServerConfig.Port)
	conn, err := tigrpcclient.NewClientConn(address, tigrpcclient.WithDefault())
	if err != nil {
		return nil, nil, err
	}

	mux := runtime.NewServeMux(
		runtime.WithForwardResponseOption(httpResponseModifier),
		runtime.WithHealthEndpointAt(healthv1.NewHealthClient(conn), "/healthz"),
		runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{
			MarshalOptions: protojson.MarshalOptions{
				UseProtoNames:     false,
				EmitDefaultValues: true,
			},
			UnmarshalOptions: protojson.UnmarshalOptions{
				DiscardUnknown: true,
			},
		}),
	)

	// Use "/service" as the namespace for business handlers.
	// Upstream api gateway need to route requests to "/service" path.
	inf.GinEngine().Any(fmt.Sprintf("%s/*any", baseAPIPath), func(c *gin.Context) {
		c.Request.URL.Path = c.Param("any")
		mux.ServeHTTP(c.Writer, c.Request)
	})

	inf.GinEngine().Any(fmt.Sprintf("%s/*any", httpRouteAPIPath), func(c *gin.Context) {
		c.Request.URL.Path = c.Param("any")
		mux.ServeHTTP(c.Writer, c.Request)
	})

	inf.GinEngine().Any(fmt.Sprintf("%s/*any", regionalPath), func(c *gin.Context) {
		c.Request.URL.Path = c.Param("any")
		mux.ServeHTTP(c.Writer, c.Request)
	})

	return mux, conn, nil
}

func httpResponseModifier(ctx context.Context, w http.ResponseWriter, p proto.Message) error {
	md, ok := runtime.ServerMetadataFromContext(ctx)
	if !ok {
		return nil
	}

	// set http status code
	if vals := md.HeaderMD.Get(responseKeyStatusCode); len(vals) > 0 {
		code, err := strconv.Atoi(vals[0])
		if err != nil {
			return err
		}
		// delete the headers to not expose any grpc-metadata in http response
		delete(md.HeaderMD, responseKeyStatusCode)
		delete(w.Header(), fmt.Sprintf("Grpc-Metadata-%s", textproto.CanonicalMIMEHeaderKey(responseKeyStatusCode)))
		w.WriteHeader(code)
	}

	return nil
}
