package security

import (
	"encoding/json"
	"fmt"
	"image/color"
	"io"
	"net/http"
	"strconv"
	"strings"
	"time"

	"be.ems/features/security/service"
	sysConfigService "be.ems/features/sys_config/service"
	"be.ems/lib/core/account"
	"be.ems/lib/core/cache"
	"be.ems/lib/core/constants/cachekey"
	"be.ems/lib/core/utils/ctx"
	"be.ems/lib/core/vo/result"
	"be.ems/lib/dborm"
	"be.ems/lib/global"
	"be.ems/lib/log"
	"be.ems/lib/oauth"
	"be.ems/lib/services"
	"be.ems/restagent/config"
	srcConfig "be.ems/src/framework/config"
	"be.ems/src/framework/redis"
	"github.com/mojocn/base64Captcha"
)

var (
	UriOauthToken     = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token"
	UriOauthHandshake = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/handshake"

	CustomUriOauthToken     = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/token"
	CustomUriOauthHandshake = config.UriPrefix + "/securityManagement/{apiVersion}/{elementTypeValue}/handshake"

	// 系统登录
	UriLogin       = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/login"
	CustomUriLogin = config.UriPrefix + "/securityManagement/{apiVersion}/login"

	// 获取验证码
	UriCaptchaImage       = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/captchaImage"
	CustomUriCaptchaImage = config.UriPrefix + "/securityManagement/{apiVersion}/captchaImage"

	// 登录用户信息
	UriUserInfo       = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/getUserInfo"
	CustomUriUserInfo = config.UriPrefix + "/securityManagement/{apiVersion}/getUserInfo"

	// 登录用户路由信息
	UriRouters       = config.DefaultUriPrefix + "/securityManagement/{apiVersion}/getRouters"
	CustomUriRouters = config.UriPrefix + "/securityManagement/{apiVersion}/getRouters"
)

func LoginFromOMC(w http.ResponseWriter, r *http.Request) {
	log.Info("LoginFromOMC processing... ")

	body, err := io.ReadAll(io.LimitReader(r.Body, global.RequestBodyMaxLen)) //io.LimitReader限制大小
	if err != nil {
		log.Error("Failed to ReadAll:", err)
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	// check media type(content type) only support "application/json"
	if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) {
		log.Debug("Invalid Content-Type")
		services.ResponseUnsupportedMediaType415(w)
		return
	}

	// // check extend uri, response 404
	// if !IsValidOAuthUri(r) {
	// 	log.Debug("Uri is invalid")
	// 	services.ResponseNotFound404UriNotExist(w, r)
	// 	return
	// }

	// Error process ....
	// response 400-7
	if !json.Valid([]byte(body)) {
		log.Error("Invalid Json Format")
		services.ResponseBadRequest400InvalidJson(w)
		return
	}

	var oAuthBody oauth.OAuthBody
	_ = json.Unmarshal(body, &oAuthBody) //转为json
	//log.Debug("body:", string(body), "oAuthBody:", oAuthBody)

	defer r.Body.Close()
	// response 400-5
	if oauth.IsWrongOAuthInfo(oAuthBody) {
		log.Error("Wrong parameter value")
		services.ResponseBadRequest400WrongParamValue(w)
		return
	}
	/*
		if oauth.IsValidOAuthInfo(oAuthBody) {
			plist := config.GetPermissionFromConfig(oAuthBody.UserName, oAuthBody.GrantType)
			log.Debug("Permission list:", plist)

			token := globalSession.NewSession(w, r, plist)
			services.ResponseStatusOK200Login(w, token)
		} else {
			// response 400-4
			log.Debug("Authentication failed, mismatch user or password")

			services.ResponseBadRequest400IncorrectLogin(w)
		}
	*/
	validUser, user, err := dborm.XormCheckLoginUser(oAuthBody.UserName,
		oAuthBody.Value, config.GetYamlConfig().Auth.Crypt)
	if !validUser || err != nil {
		// response 400-4
		log.Error("Authentication failed, mismatch user or password")
		services.ResponseErrorWithJson(w, 400, err.Error())
		return
	}

	token := oauth.GenRandToken("omc") // Generate new token to session ID
	sourceAddr := r.RemoteAddr[:strings.Index(r.RemoteAddr, ":")]
	affected, err := dborm.XormInsertSession(oAuthBody.UserName, sourceAddr, token,
		config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session)
	if err != nil {
		log.Error("Failed to XormInsertSession:", err)
		if affected == -1 {
			services.ResponseForbidden403MultiLoginNotAllowed(w)
		} else {
			services.ResponseBadRequest400IncorrectLogin(w)
		}

		return
	}

	if user != nil {
		// 缓存用户信息
		account.CacheLoginUser(user)
		redis.SetByExpire("", "session_token", token, time.Second*1800)
		// 角色权限集合，管理员拥有所有权限
		userId := fmt.Sprint(user.Id)
		isAdmin := srcConfig.IsAdmin(userId)
		roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin)
		services.ResponseStatusOK200LoginWhitRP(w, token, user, roles, perms)
		return
	}
	services.ResponseBadRequest400IncorrectLogin(w)
}

func LogoutFromOMC(w http.ResponseWriter, r *http.Request) {
	log.Info("LogoutFromOMC processing... ")

	token, err := services.CheckFrontValidRequest(w, r)
	if err != nil {
		log.Error("Request error:", err)
		return
	}
	// // check media type(content type) only support "application/json"
	// if services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) == false {
	// 	log.Error("Invalid Content-Type")
	// 	services.ResponseUnsupportedMediaType415(w)
	// 	return
	// }

	// // check extend uri, response 404
	// if !services.IsValidOAuthUri(r) {
	// 	log.Error("Uri is invalid")
	// 	services.ResponseNotFound404UriNotExist(w, r)
	// 	return
	// }

	// // error processing ...
	// // 401-1 response
	// token, ret := oauth.IsCarriedToken(r)
	// if ret == false {
	// 	log.Error("AccessToken is not carried")
	// 	services.ResponseUnauthorized401AccessTokenNotCarried(w)
	// 	return
	// }

	se, err := dborm.XormLogoutUpdateSession(token)
	if err != nil {
		log.Error("Uri is invalid")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}
	// 清除缓存用户信息
	account.ClearLoginUser(se.AccountId)
	services.ResponseStatusOK200Null(w)
}

func HandshakeFromOMC(w http.ResponseWriter, r *http.Request) {
	log.Info("HandshakeFromOMC processing... ")

	// check media type(content type) only support "application/json"
	if !services.IsVallidContentType(r, config.GetYamlConfig().OMC.CheckContentType) {
		log.Debug("Invalid Content-Type")
		services.ResponseUnsupportedMediaType415(w)
		return
	}

	// check extend uri, response 404
	if !services.IsValidOAuthUri(r) {
		log.Error("Uri is invalid")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}

	// error processing ...
	// 401-1 response
	token, ret := oauth.IsCarriedToken(r)
	if !ret {
		log.Error("AccessToken is not carried")
		services.ResponseUnauthorized401AccessTokenNotCarried(w)
		return
	}

	_, err := dborm.XormUpdateSessionShakeTime(token)
	if err != nil {
		log.Error("Uri is invalid")
		services.ResponseNotFound404UriNotExist(w, r)
		return
	}
	services.ResponseStatusOK200Null(w)
}

// 系统登录
//
// POST /login
func LoginOMC(w http.ResponseWriter, r *http.Request) {
	log.Info("LoginOMC processing... ")
	var body struct {
		Username string `json:"username" binding:"required"` // Username 用户名
		Password string `json:"password" binding:"required"` // Password 用户密码
		Code     string `json:"code"`                        // Code 验证码
		UUID     string `json:"uuid"`                        // UUID 验证码唯一标识
	}
	err := ctx.ShouldBindJSON(r, &body)
	if err != nil {
		log.Error("Invalid Json Format")
		ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
		return
	}
	// response 400-5
	if body.Username == "" || body.Password == "" {
		log.Error("Wrong parameter value")
		ctx.JSON(w, 400, result.CodeMsg(400, "parameter error"))
		return
	}

	// 校验验证码
	// 从数据库配置获取验证码开关 true开启，false关闭
	captchaEnabledStr := sysConfigService.NewServiceSysConfig.SelectConfigValueByKey("sys.account.captchaEnabled")
	captchaEnabled, err := strconv.ParseBool(captchaEnabledStr)
	if err != nil {
		captchaEnabled = false
	}
	if captchaEnabled {
		if body.Code == "" || body.UUID == "" {
			log.Error("Authentication failed, mismatch captcha")
			ctx.JSON(w, 400, result.CodeMsg(400, "Verification code information error"))
			return
		}
		verifyKey := cachekey.CAPTCHA_CODE_KEY + body.UUID
		captcha, ok := cache.GetLocalTTL(verifyKey)
		if captcha == nil || !ok {
			log.Error("Authentication failed, captcha emtry")
			ctx.JSON(w, 400, result.CodeMsg(400, "The verification code has expired"))
			return
		}
		cache.DeleteLocalTTL(verifyKey)
		if captcha.(string) != body.Code {
			log.Error("Authentication failed, not match captcha")
			ctx.JSON(w, 400, result.CodeMsg(400, "Verification code error"))
			return
		}
	}

	validUser, user, err := dborm.XormCheckLoginUser(body.Username, body.Password, config.GetYamlConfig().Auth.Crypt)
	if !validUser || err != nil {
		// response 400-4
		log.Error("Authentication failed, mismatch user or password")
		ctx.JSON(w, 400, result.CodeMsg(400, err.Error()))
		return
	}

	token := oauth.GenRandToken("omc") // Generate new token to session ID
	sourceAddr := r.RemoteAddr[:strings.Index(r.RemoteAddr, ":")]
	affected, err := dborm.XormInsertSession(body.Username, sourceAddr, token,
		config.GetExpiresFromConfig(), config.GetYamlConfig().Auth.Session)
	if err != nil {
		log.Error("Failed to XormInsertSession:", err)
		if affected == -1 {
			services.ResponseForbidden403MultiLoginNotAllowed(w)
		} else {
			services.ResponseBadRequest400IncorrectLogin(w)
		}

		return
	}

	if user != nil {
		// 缓存用户信息
		account.CacheLoginUser(user)
		redis.SetByExpire("", "session_token", token, time.Second*1800)
		ctx.JSON(w, 200, result.OkData(map[string]any{
			"accessToken": token,
		}))
		return
	}
	ctx.JSON(w, 200, result.Err(nil))
}

// 获取验证码
//
// GET /captchaImage
func CaptchaImage(w http.ResponseWriter, r *http.Request) {
	configService := sysConfigService.NewServiceSysConfig

	// 从数据库配置获取验证码开关 true开启，false关闭
	captchaEnabledStr := configService.SelectConfigValueByKey("sys.account.captchaEnabled")
	captchaEnabled, err := strconv.ParseBool(captchaEnabledStr)
	if err != nil {
		captchaEnabled = false
	}
	if !captchaEnabled {
		ctx.JSON(w, 200, result.Ok(map[string]any{
			"captchaEnabled": captchaEnabled,
		}))
		return
	}

	// 生成唯一标识
	verifyKey := ""
	data := map[string]any{
		"captchaEnabled": captchaEnabled,
		"uuid":           "",
		"img":            "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
	}

	// char 字符验证
	driverCaptcha := &base64Captcha.DriverString{
		//Height png height in pixel.
		Height: 40,
		// Width Captcha png width in pixel.
		Width: 120,
		//NoiseCount text noise count.
		NoiseCount: 4,
		//Length random string length.
		Length: 4,
		//Source is a unicode which is the rand string from.
		Source: "023456789abcdefghjkmnprstuvwxyz",
		//ShowLineOptions := OptionShowHollowLine | OptionShowSlimeLine | OptionShowSineLine .
		ShowLineOptions: base64Captcha.OptionShowHollowLine,
		//BgColor captcha image background color (optional)
		BgColor: &color.RGBA{
			R: 250,
			G: 250,
			B: 250,
			A: 255, // 不透明
		},
	}
	// 验证码生成
	id, question, answer := driverCaptcha.GenerateIdQuestionAnswer()
	// 验证码表达式解析输出
	item, err := driverCaptcha.DrawCaptcha(question)
	if err != nil {
		log.Infof("Generate Id Question Answer %s : %v", question, err)
	} else {
		data["uuid"] = id
		data["img"] = item.EncodeB64string()
		verifyKey = cachekey.CAPTCHA_CODE_KEY + id
		cache.SetLocalTTL(verifyKey, answer, 120*time.Second)
	}

	// 本地开发下返回验证码结果，方便接口调试
	// text, ok := cache.GetLocalTTL(verifyKey)
	// if ok {
	// 	data["text"] = text.(string)
	// }

	ctx.JSON(w, 200, result.Ok(data))
}

// 登录用户信息
func UserInfo(w http.ResponseWriter, r *http.Request) {
	loginUser, err := ctx.LoginUser(r)
	if err != nil {
		ctx.JSON(w, 200, result.OkData(err.Error()))
	}
	// 角色权限集合，管理员拥有所有权限
	userId := fmt.Sprint(loginUser.UserID)
	isAdmin := srcConfig.IsAdmin(userId)
	roles, perms := service.NewServiceAccount.RoleAndMenuPerms(userId, isAdmin)

	ctx.JSON(w, 200, result.OkData(map[string]any{
		"user":        loginUser.User,
		"roles":       roles,
		"permissions": perms,
	}))
}

// 登录用户路由信息
func Routers(w http.ResponseWriter, r *http.Request) {
	userID := ctx.LoginUserToUserID(r)

	// 前端路由，管理员拥有所有
	isAdmin := srcConfig.IsAdmin(userID)
	buildMenus := service.NewServiceAccount.RouteMenus(userID, isAdmin)
	ctx.JSON(w, 200, result.OkData(buildMenus))
}
