package api

import (
	"context"
	"net/http"

	"dataapps/commons/grpc"
	"dataapps/consts"
	"dataapps/internal/controller/sqleditor"
	dto "dataapps/internal/dto/sqleditor"

	tierr "github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"

	"github.com/gin-gonic/gin"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/dataservices/idl/pbgen/proto/dataapi"
)

func DataApiRouters(eng *gin.Engine) {
	eng.Any(
		"/healthz", func(c *gin.Context) {
			c.Status(200)
		},
	)
	// Metric
	eng.Use(Metric())

	// Error
	routeError(eng)

	// Cors
	routeCors(eng)

	eng.Use(CheckPathVersion)

	// Auth ApiKey
	eng.Use(AuthApiKey)

	// Request Body Check
	eng.Use(CheckRequestBodySize)

	// /api/v1beta/apps/:app_id/v1/chat2data
	eng.POST("/api/:api_ver/apps/:app_id/:app_ver/chat2data", sqleditor.Chat2Data)
	eng.POST("/api/:api_ver/apps/:app_id/:app_ver/chat2question", sqleditor.Chat2Question)
	eng.POST("/api/:api_ver/apps/:app_id/:app_ver/chat2chart", sqleditor.Chat2Chart)
	eng.POST("/api/:api_ver/apps/:app_id/:app_ver/data2column-match", sqleditor.Data2ColumnMatching)
	eng.GET("/api/:api_ver/apps/:app_id/:app_ver/streaming", sqleditor.QueryStreamResultByJobID)
}

var supportApiVers = map[string]struct{}{
	"v1beta": {},
}

var supportAppVers = map[string]struct{}{
	"v1": {},
}

func CheckPathVersion(c *gin.Context) {
	apiVer := c.Param("api_ver")
	if _, ok := supportApiVers[apiVer]; !ok {
		checkFailRes(c, http.StatusBadRequest, "path api_version invalid")
		c.Abort()
		return
	}
	appVer := c.Param("app_ver")
	if _, ok := supportAppVers[appVer]; !ok {
		checkFailRes(c, http.StatusBadRequest, "path app_version invalid")
		c.Abort()
		return
	}
}

func checkFailRes(c *gin.Context, code int, msg string) {
	c.JSON(code, &dto.BasicRes{
		Code:    code,
		Message: msg,
	})
}

func AuthApiKey(c *gin.Context) {
	ctx := c.Request.Context()
	apiKey, err := getApiKey(c)
	if err != nil {
		tilog.Info(ctx, "GetApiKey failed. "+err.Error())
		checkFailRes(c, err.HTTPCode(), err.ErrMsg())
		c.Abort()
		return
	}
	appID, err := getAppID(c)
	if err != nil {
		tilog.Info(ctx, "GetAppID failed. "+err.Error())
		checkFailRes(c, err.HTTPCode(), err.ErrMsg())
		c.Abort()
		return
	}

	authed, err := doAuthApiKey(ctx, apiKey, appID)
	if err != nil {
		tilog.Info(ctx, "doAuthApiKey failed. "+err.Error())
		checkFailRes(c, http.StatusUnauthorized, err.ErrMsg())
		c.Abort()
		return
	}
	if !authed {
		tilog.Info(ctx, "auth is false ")
		checkFailRes(c, http.StatusUnauthorized, "api key auth failed")
		c.Abort()
		return
	}

	appInfo, err := parseAppInfo(ctx, appID)
	if err != nil {
		checkFailRes(c, http.StatusInternalServerError, err.ErrMsg())
		c.Abort()
		return
	}

	var chat2QueryAuthInfo = &dto.Chat2QueryAuthInfo{
		ApiKey:    apiKey,
		AppID:     appID,
		OrgID:     appInfo.OrgId,
		ProjectID: appInfo.ProjectId,
	}

	c.Set(consts.CONTEXT_CURRENT_API_KEY, chat2QueryAuthInfo)
	c.Next()
}

func doAuthApiKey(ctx context.Context, apiKey, appID string) (bool, tierr.Error) {
	req := &dataapi.AuthAPIKeyReq{
		DataappExtId: appID,
		ApiKey:       apiKey,
	}
	rsp, err := grpc.AuthAPIKey(ctx, req)
	if err != nil {
		return false, tierr.NewInternalGRPC("AuthAPIKey", err)
	}
	return rsp.AuthFlag, nil
}

func parseAppInfo(ctx context.Context, appID string) (*dataapi.AppInfo, tierr.Error) {
	req := &dataapi.ShowDataAppReq{
		AppExtId: appID,
	}
	rsp, err := grpc.ShowDataApp(ctx, req)
	if err != nil {
		return nil, tierr.NewInternalGRPC("ShowDataApp", err)
	}
	return rsp.AppInfo, nil
}

func getAppID(c *gin.Context) (string, tierr.Error) {
	appID := c.Param("app_id")
	if appID == "" {
		// impossible
		msg := "X-Auth-Content header not found"
		return "", tierr.NewBadRequest(msg, errors.New(msg))
	}
	return appID, nil
}

func getApiKey(c *gin.Context) (string, tierr.Error) {
	apiKey := c.GetHeader("api-key")
	if apiKey == "" {
		msg := "api-key header not found"
		return "", tierr.NewUnauthorized(msg, errors.New(msg))
	}
	return apiKey, nil
}
