package eda

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"path"
	"strconv"
	"strings"

	"dataapps/commons/config"
	"dataapps/commons/httpclient"
	"dataapps/consts"
	dto "dataapps/internal/dto/sqleditor"

	"github.com/gin-gonic/gin"
	"github.com/tidbcloud/tiinf/errs"
	"github.com/tidbcloud/tiinf/monitor/tilog"
	"go.uber.org/zap"
)

const ERRORID = 0

func getToken(c *gin.Context) (string, error) {
	header := c.GetHeader("authorization")
	if header == "" {
		/*
		   since HTTP/2 use a lower case header, HTTP/1.x header is case-insensitive,
		   but generally we use a Authorization header in HTTP/1.x
		   LINK:
		   https://tools.ietf.org/html/rfc7540#section-8.1.2
		   https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
		*/
		header = c.GetHeader("Authorization")
	}

	if header == "" {
		return "", errs.NewUnauthorized("Token not found", nil)
	}

	tokenSlice := strings.Split(header, " ")
	if len(tokenSlice) != 2 || strings.ToLower(tokenSlice[0]) != "bearer" {
		return "", errs.NewUnauthorized("Invalid token", nil)
	}

	return tokenSlice[1], nil
}

func getParam(c *gin.Context) (uint64, uint64, uint64, error) {
	var (
		orgId     uint64
		projectId uint64
		clusterId uint64
	)
	orgIdStr := c.Param("org_id")
	orgId, err := strconv.ParseUint(orgIdStr, 10, 64)
	if err != nil {
		return ERRORID, ERRORID, ERRORID, err
	}

	projectIdStr := c.Param("project_id")
	projectId, err = strconv.ParseUint(projectIdStr, 10, 64)
	if err != nil {
		return ERRORID, ERRORID, ERRORID, err
	}

	clusterIdStr := c.Param("cluster_id")
	if clusterIdStr != "" {
		clusterId, err = strconv.ParseUint(clusterIdStr, 10, 64)
		if err != nil {
			return ERRORID, ERRORID, ERRORID, err
		}
	}
	return orgId, projectId, clusterId, nil
}

// a proxy handler for eda
func EDAProxy(c *gin.Context) {
	req := c.Request
	ctx := c.Request.Context()
	method := c.Request.Method

	u, ok := c.Get(consts.CONTEXT_CURRENT_USER)
	if !ok {
		tilog.Error(ctx, "failed to get current user from context")
		c.AbortWithError(http.StatusUnauthorized, errs.NewUnauthorized("login required", nil))
		return
	}
	user, ok := u.(*dto.User)
	if !ok {
		tilog.Error(ctx, "failed to get current user from context")
		c.AbortWithError(http.StatusUnauthorized, errs.NewUnauthorized("login required", nil))
		return
	}
	orgId, projectId, _, err := getParam(c)
	if err != nil {
		c.Error(err)
		tilog.Error(ctx, "ERROR: get orgId,projectId,clusterId failed! user:"+user.Email)
		return
	}

	url := fmt.Sprintf("%s%s", config.Global().ServerConfig.EDASvcAddr, path.Join("/inner/eda", req.URL.Path))

	var (
		queryString = map[string]string{}
		header      = map[string]string{}
		payload     = map[string]interface{}{}
	)

	// forward query string
	for k, v := range req.URL.Query() {
		if len(v) > 0 {
			queryString[k] = v[0] // it's a restrict of tiinf, as the query string is a map[string]string
		}
	}
	// forward header
	for k, v := range req.Header {
		if len(v) > 0 {
			header[k] = v[0] // it's a restrict of tiinf, as the header is a map[string]string
		}
	}
	userToken, err := getToken(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: get token failed", zap.String("email", user.Email), zap.Error(err))
		return
	}

	header["X-ORG-ID"] = fmt.Sprintf("%d", orgId)
	header["X-PROJECT-ID"] = fmt.Sprintf("%d", projectId)
	header["X-User-ID"] = fmt.Sprintf("%d", user.Id)
	header["X-Created-By"] = user.Email
	header["X-User-Token"] = userToken
	header["B3"] = c.GetHeader("B3")

	// forward payload
	data, err := io.ReadAll(req.Body)
	if err != nil {
		if err != io.EOF {
			tilog.Error(ctx, "failed to read request payload", zap.Error(err))
			c.AbortWithError(http.StatusBadRequest, errs.NewBadRequest("invalid request payload, please check your payload is property json format", err))
			return
		}
	} else if len(data) > 0 {
		if err = json.Unmarshal(data, &payload); err != nil {
			tilog.Error(ctx, "failed to read request payload", zap.Error(err), zap.String("data", string(data)))
			c.AbortWithError(http.StatusBadRequest, errs.NewBadRequest("invalid request payload, please check your payload is property json format", err))
			return
		}
	}

	tilog.Info(ctx, "request eda service", zap.String("method", method), zap.String("url", url), zap.Any("queryString", queryString), zap.Any("header", header))
	resp, httpCode, err := httpclient.RequestWithHeader(ctx, method, url, &queryString, &header, payload)
	tilog.Info(ctx, "request eda service resp", zap.String("method", method), zap.String("url", url), zap.Any("queryString", queryString), zap.Any("header", header), zap.String("resp", resp), zap.Int("httpCode", httpCode), zap.Error(err))
	if err != nil {
		c.AbortWithError(http.StatusInternalServerError, errs.NewInternalHTTP("failed to request eda", err))
		return
	}

	c.Status(httpCode)
	c.Header("Content-Type", "application/json")
	c.Writer.WriteString(resp)
}

func EDAChat2SQL(ctx context.Context, raw_question, database, clusterID, sessionID string, c *gin.Context) {
	// *NOTE* Please only read the gin Context variable `c`, do not use it to write anything

	orgID, projectID, _, err := getParam(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: get orgId,projectId,clusterId failed!", zap.Error(err))
		return
	}
	url := fmt.Sprintf("%s/inner/eda/api/v1/eda/orgs/%d/projects/%d/chat2sql", config.Global().ServerConfig.EDASvcAddr, orgID, projectID)
	payload := map[string]interface{}{
		"raw_question": raw_question,
		"cluster_id":   clusterID,
		"database":     database,
	}

	u, ok := c.Get(consts.CONTEXT_CURRENT_USER)
	if !ok {
		tilog.Error(ctx, "failed to get current user from context")
		return
	}
	user, ok := u.(*dto.User)
	if !ok {
		tilog.Error(ctx, "failed to get current user from context")
		return
	}

	userToken, err := getToken(c)
	if err != nil {
		tilog.Error(ctx, "ERROR: get token failed", zap.String("email", user.Email), zap.Error(err))
		return
	}

	header := map[string]string{
		"X-ORG-ID":     fmt.Sprintf("%d", orgID),
		"X-PROJECT-ID": fmt.Sprintf("%d", projectID),
		"X-User-ID":    fmt.Sprintf("%d", user.Id),
		"X-Session-ID": sessionID,
		"X-Created-By": user.Email,
		"X-User-Token": userToken,
	}
	resp, httpCode, err := httpclient.RequestWithHeader(ctx, http.MethodPost, url, nil, &header, payload)
	tilog.Info(ctx, "request eda service resp", zap.String("method", http.MethodPost), zap.String("url", url), zap.Any("header", header), zap.String("resp", resp), zap.Int("httpCode", httpCode), zap.Error(err))
}
