package router

import (
	"bytes"
	"context"
	"errors"
	"io"
	"net/http"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/go-redis/redis/v8"
	jsoniter "github.com/json-iterator/go"
	mhayaTime "github.com/mhaya/extend/time"
	cfacade "github.com/mhaya/facade"
	"github.com/mhaya/game/game_cluster/internal/code"
	"github.com/mhaya/game/game_cluster/internal/constant"
	"github.com/mhaya/game/game_cluster/internal/mdb"
	"github.com/mhaya/game/game_cluster/internal/mdb/eventmodels"
	"github.com/mhaya/game/game_cluster/internal/mdb/models"
	"github.com/mhaya/game/game_cluster/internal/param"
	rpcLogstash "github.com/mhaya/game/game_cluster/internal/rpc/logstash"
	"github.com/mhaya/game/game_cluster/nodes/webadmin/common"
	"github.com/mhaya/game/game_cluster/nodes/webadmin/entity"
	mhayaLogger "github.com/mhaya/logger"
	"go.mongodb.org/mongo-driver/bson"
	"go.mongodb.org/mongo-driver/mongo"
)

var (
	json = jsoniter.ConfigCompatibleWithStandardLibrary
)

func Auth(app cfacade.IApplication) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 请求开始时间
		startTime := mhayaTime.Now().UnixMilli()

		tokenString := ""
		roleId := ""
		var err error

		defer func() {
			sendLogRecord(c, app, tokenString, roleId, startTime)
		}()

		tokenString = c.GetHeader("Token")
		if tokenString == "" {
			common.PackUnauthorizedResult(c, code.UnauthorizedError, "token is empty")
			return
		}

		roleId, err = mdb.RDB.Get(context.Background(), tokenString).Result()
		if err != nil && err != redis.Nil {
			mhayaLogger.Warnf("Auth Get error: %s", err.Error())
			common.PackUnauthorizedResult(c, code.InternalError, "token is empty")
			return
		}
		if roleId == "" {
			common.PackUnauthorizedResult(c, code.UnauthorizedError, "token is invalid")
			return
		}

		if roleId != constant.AdminAccess {
			urlAccess, err := mdb.RDB.HGet(context.Background(), common.GetTokenKey(tokenString), c.Request.URL.Path).Result()
			if err != nil {
				mhayaLogger.Warnf("Auth HGet s error: %s", err.Error())
				common.PackUnauthorizedResult(c, code.InternalError, "")
				return
			}

			// 检查url权限
			if urlAccess == "" {
				common.PackUnauthorizedResult(c, code.UnauthorizedError, "token is no auth")
				return
			}

			// 非管理员需要进行ip校验
			openIpWhitelist := app.Settings().Get("open_ip_whitelist").ToBool()
			if openIpWhitelist {
				err = checkIPWhitelist(c)
				if err != nil {
					mhayaLogger.Warnf("Auth checkIPWhitelist error: %s", err.Error())
					common.PackForbiddenResult(c, code.ForbiddenError, "ip is no auth")
					return
				}
			}
		}

		adminAccess, err := mdb.RDB.HGet(context.Background(), common.GetTokenKey(tokenString), constant.AdminAccess).Result()
		if err != nil {
			mhayaLogger.Warnf("Auth HGet ss error: %s", err.Error())
			common.PackUnauthorizedResult(c, code.InternalError, "")
			return
		}

		// 检查管理员权限
		if adminAccess == "" {
			common.PackUnauthorizedResult(c, code.UnauthorizedError, "token is no auth")
			return
		}

		c.Next()
	}
}

// checkIP
func checkIPWhitelist(c *gin.Context) error {
	// 获取请求的ip
	ip := c.ClientIP()
	var whitelistModel *models.Whitelist
	collection := mdb.MDB.Collection(whitelistModel.TableName())

	// 设置超时时间
	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel() // 确保在函数退出时取消上下文

	// 示例：查询 IP 是否在白名单中
	err := collection.FindOne(ctx, bson.M{"ip": ip}).Decode(&whitelistModel)
	if err != nil && err != mongo.ErrNoDocuments {
		return err
	}

	// 根据查询结果决定是否允许访问
	if whitelistModel == nil {
		return errors.New("IP not in whitelist") // 拒绝访问
	}

	return nil // 允许访问
}

func sendLogRecord(c *gin.Context, app cfacade.IApplication, tokenString, roleId string, startTime int64) {
	userName, _ := mdb.RDB.Get(context.Background(), common.GetUserNameKey(tokenString)).Result()
	nodeId := app.NodeId()
	req, err := packBackendOperationEventReq(nodeId, &eventmodels.BackendOperationEventContent{
		UserBasic: eventmodels.UserBasic{
			UserName: userName,
			IsRobot:  false,
		},
		EventBasic: eventmodels.EventBasic{
			ServerId: nodeId,
			IsSuccess: func() bool {
				return c.Writer.Status() == http.StatusOK
			}(),
			CreateAt: mhayaTime.Now().Unix(),
		},
		RoleId:       roleId,
		Path:         c.Request.URL.Path,
		Method:       c.Request.Method,
		StatusCode:   c.Writer.Status(),
		Dur:          mhayaTime.Now().UnixMilli() - startTime,
		ClientIP:     c.ClientIP(),
		ErrorMessage: c.Errors.ByType(gin.ErrorTypePrivate).String(),
	})
	if err != nil {
		mhayaLogger.Warnf("sendLogRecord packBackendOperationEventReq error: %s, token: %s", err.Error(), tokenString)
		return
	}

	rpcLogstash.HandleLogRecord(app, req)
}

func packBackendOperationEventReq(nodeId string, content *eventmodels.BackendOperationEventContent) (*param.HandleLogReq, error) {
	bytes, err := json.Marshal(content)
	if err != nil {
		return nil, err
	}

	return &param.HandleLogReq{
		ServerId:    nodeId,
		EventName:   content.EventName(),
		JsonContent: string(bytes),
	}, nil
}

func loginRecordMiddleware(app cfacade.IApplication) gin.HandlerFunc {
	return func(c *gin.Context) {
		// 请求开始时间
		startTime := mhayaTime.Now().UnixMilli()

		body, err := io.ReadAll(c.Request.Body)
		if err != nil {
			mhayaLogger.Warnf("loginRecordMiddleware ReadAll Request.Body error: %s", err.Error())
			return
		}
		// 关闭body
		defer c.Request.Body.Close()

		var reqBody entity.AdminLoginReq
		err = json.Unmarshal(body, &reqBody)
		if err != nil {
			mhayaLogger.Warnf("loginRecordMiddleware Unmarshal Request.Body error: %s", err.Error())
			return
		}

		// 写回请求体，以便其他中间件或路由可以正常读取
		c.Request.Body = io.NopCloser(bytes.NewBuffer(body))

		c.Next()

		nodeId := app.NodeId()
		req, err := packBackendOperationEventReq(nodeId, &eventmodels.BackendOperationEventContent{
			UserBasic: eventmodels.UserBasic{
				UserName: reqBody.Username,
				IsRobot:  false,
			},
			EventBasic: eventmodels.EventBasic{
				ServerId: nodeId,
				IsSuccess: func() bool {
					return c.Writer.Status() == http.StatusOK
				}(),
				CreateAt: mhayaTime.Now().Unix(),
			},
			Path:         c.Request.URL.Path,
			Method:       c.Request.Method,
			StatusCode:   c.Writer.Status(),
			Dur:          mhayaTime.Now().UnixMilli() - startTime,
			ClientIP:     c.ClientIP(),
			ErrorMessage: c.Errors.ByType(gin.ErrorTypePrivate).String(),
		})
		if err != nil {
			mhayaLogger.Warnf("loginRecordMiddleware packBackendOperationEventReq error: %s", err.Error())
			return
		}

		rpcLogstash.HandleLogRecord(app, req)
	}
}
