package pub

import (
	"crypto/md5"
	"encoding/hex"
	"errors"
	"fmt"
	"strconv"

	"bsm/full/module/base/mgt/internal/impl"
	"bsm/full/module/base/mgt/internal/models"
	"bsm/full/module/base/mgt/internal/types"

	"git.apinb.com/bsm-sdk/core/crypto/token"
	"git.apinb.com/bsm-sdk/core/env"
	"git.apinb.com/bsm-sdk/core/errcode"
	"git.apinb.com/bsm-sdk/core/infra"
	"git.apinb.com/bsm-sdk/core/printer"
	"git.apinb.com/bsm-sdk/core/vars"
	"github.com/gin-gonic/gin"
	"golang.org/x/crypto/bcrypt"
	"gorm.io/gorm"
)

// Login 登录
// @Summary 登录
// @Description 登录
// @Tags 登录
// @Accept application/json
// @Produce application/json
// @Param role body types.LoginRequest true "登录请求"
// @Security ApiKeyAuth
// @Success 200 {object} types.LoginResp "新增成功,返回token"
// @Router /login [post]
func Login(c *gin.Context) {
	var (
		request types.LoginRequest
		resp    = types.LoginResp{}
		user    = models.MgtUser{}
		extend  = map[string]string{}
		err     error
		account string
		pwd     string
	)
	err = c.ShouldBindJSON(&request)
	if err != nil {
		infra.Response.Error(c, errcode.ErrJsonUnmarshal)
		return
	}

	if request.Account != "" && request.Password != "" {
		account = request.Account
		pwd = request.Password
	} else if request.AppId != "" && request.AppKey != "" {
		account = request.AppId
		pwd = request.AppKey
	} else {
		printer.Error("登录参数异常: 账号密码或AppId/AppKey必须提供")
		infra.Response.Error(c, errcode.ErrInvalidArgument)
		return
	}

	// 获取用户信息
	if err = impl.DBService.Model(&models.MgtUser{}).Where("account=?", account).First(&user).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			infra.Response.Error(c, errcode.ErrRecordNotFound)
			return
		}
		infra.Response.Error(c, errcode.ErrDB)
		return
	}
	resp = types.LoginResp{Phone: user.Phone, Email: user.Email, Name: user.Name, UserId: user.ID, Account: user.Account, Avatar: user.Avatar, Identity: user.Identity}
	// 检测Password与Status
	if request.AppKey != "" {
		err = checkAppKeyAndStatus(user.Password, pwd, int16(user.Status))
	} else {
		err = checkPwdAndStatus(user.Password, pwd, user.Salt, int16(user.Status))
	}
	if err != nil {
		printer.Error("检测Password与Status异常: %v", err)
		infra.Response.Error(c, err)
		return
	}
	extend["id"] = strconv.Itoa(int(user.ID))
	extend["Identity"] = user.Identity
	extend["status"] = strconv.Itoa(int(user.Status))
	extend["name"] = user.Name

	// 获取token
	resp.Token, err = token.New(env.Runtime.JwtSecretKey).GenerateJwt(uint(user.ID), user.Identity, c.ClientIP(), "", nil, extend)
	if err != nil {
		printer.Error("获取token异常: %v", err)
		infra.Response.Error(c, err)
		return
	}

	err = models.UpdateLastLoginDate(uint(user.ID))
	if err != nil {
		printer.Error("更新最后登录时间异常: %v", err)
		infra.Response.Error(c, errcode.ErrInternal)
		return
	}

	printer.Info("用户登录成功: account=%s, userId=%d", account, user.ID)
	infra.Response.Success(c, resp)
}

func SaveToken(id int64, role, token string) error {
	tokenKey := fmt.Sprintf("%d-%s-%s", uint(id), role, "token")
	status := impl.RedisService.Client.Set(impl.RedisService.Ctx, tokenKey, token, 0)
	if status.Val() != "OK" {
		printer.Error("保存token失败: %v", errcode.ErrTokenAuthTokenChanged)
		return errcode.ErrTokenAuthTokenChanged
	}
	return nil
}

func checkPwdAndStatus(userPwd, inPwd, salt string, stat int16) error {
	// 账号是否禁用
	if stat == vars.DisabledStatus {
		printer.Error("检查账号状态异常: 账号已禁用")
		return errcode.ErrUnavailable
	}

	err := bcrypt.CompareHashAndPassword([]byte(userPwd), []byte(inPwd+salt))
	if err == nil {
		return nil
	}

	// 如果bcrypt失败，尝试MD5验证（向后兼容旧用户）
	isBcrypt := len(userPwd) > 4 && (userPwd[0:4] == "$2a$" || userPwd[0:4] == "$2b$" || userPwd[0:4] == "$2y$")
	if isBcrypt {
		printer.Error("密码验证失败: bcrypt验证失败")
		return errcode.ErrPassword
	}

	hash := md5.Sum([]byte(inPwd + salt))
	md5Hash := hex.EncodeToString(hash[:])
	if userPwd == md5Hash {
		return nil
	}

	printer.Error("密码验证失败: 所有验证方式均失败")
	return errcode.ErrPassword
}

func checkAppKeyAndStatus(userPwd, pwd string, stat int16) error {
	if userPwd != pwd {
		printer.Error("检查密码异常: 密码不匹配")
		return errcode.ErrPassword
	}

	if stat == vars.DisabledStatus {
		printer.Error("检查账号状态异常: 账号已禁用")
		return errcode.ErrUnavailable
	}

	return nil
}
