package model

import (
	"encoding/json"
	"errors"
	"fmt"
	"github.com/golang-jwt/jwt/v5"
	"github.com/loveuer/nfflow/internal/opt"
	"github.com/loveuer/nfflow/internal/sqlType"
	"github.com/loveuer/nfflow/internal/util"
	"github.com/samber/lo"
	"github.com/sirupsen/logrus"
	"github.com/spf13/cast"
	"strings"
	"time"
)

var (
	initUsers = []*User{
		{
			Id:       1,
			Username: "root",
			Password: util.NewPassword("404@Ro0t"),
			Nickname: "admin",
			Role:     RoleRoot,
			//Privileges: sqlType.NumSlice[Privilege]{
			//	PrivilegeUserManage,
			//},
			Privileges: lo.Map(Privilege(0).All(), func(item Enum, index int) Privilege {
				return item.(Privilege)
			}),
			CreatedById:   1,
			CreatedByName: "admin",
			ActiveAt:      time.Now().UnixMilli(),
			Deadline:      time.Now().AddDate(100, 0, 0).UnixMilli(),
		},
		{
			Id:       2,
			Username: "admin",
			Password: util.NewPassword("Foobar123"),
			Nickname: "admin",
			Role:     RoleAdmin,
			Privileges: lo.Map(Privilege(0).All(), func(item Enum, index int) Privilege {
				return item.(Privilege)
			}),
			CreatedById:   1,
			CreatedByName: "admin",
			ActiveAt:      time.Now().UnixMilli(),
			Deadline:      time.Now().AddDate(100, 0, 0).UnixMilli(),
		},
		{
			Id:       3,
			Username: "user",
			Password: util.NewPassword("Foobar123"),
			Nickname: "user",
			Role:     RoleUser,
			Privileges: sqlType.NumSlice[Privilege]{
				PrivilegeOpLog,
			},
			CreatedById:   2,
			CreatedByName: "admin",
			ActiveAt:      time.Now().UnixMilli(),
			Deadline:      time.Now().AddDate(100, 0, 0).UnixMilli(),
		},
	}

	_ Enum = Status(0)
)

type Status uint64

const (
	StatusNormal Status = iota
	StatusFrozen
)

func (s Status) Value() int64 {
	return int64(s)
}

func (s Status) Code() string {
	switch s {
	case StatusNormal:
		return "normal"
	case StatusFrozen:
		return "frozen"
	default:
		return "unknown"
	}
}

func (s Status) Label() string {
	switch s {
	case StatusNormal:
		return "正常"
	case StatusFrozen:
		return "冻结"
	default:
		return "异常"
	}
}

func (s Status) All() []Enum {
	return []Enum{
		StatusNormal,
		StatusFrozen,
	}
}

func (s Status) MarshalJSON() ([]byte, error) {
	return json.Marshal(map[string]any{
		"value": s.Value(),
		"code":  s.Code(),
		"label": s.Label(),
	})
}

type User struct {
	Id        uint64 `json:"id" gorm:"primaryKey;column:id"`
	CreatedAt int64  `json:"created_at" gorm:"column:created_at;autoCreateTime:milli"`
	UpdatedAt int64  `json:"updated_at" gorm:"column:updated_at;autoUpdateTime:milli"`
	DeletedAt int64  `json:"deleted_at" gorm:"index;column:deleted_at;default:0"`

	Username string `json:"username" gorm:"column:username;type:varchar(64);unique"`
	Password string `json:"-" gorm:"column:password;type:varchar(256)"`

	Status Status `json:"status" gorm:"column:status;default:0"`

	Nickname string `json:"nickname" gorm:"column:nickname;type:varchar(64)"`
	Comment  string `json:"comment" gorm:"column:comment"`

	Role       Role                        `json:"role" gorm:"column:role"`
	Privileges sqlType.NumSlice[Privilege] `json:"privileges" gorm:"column:privileges;type:bigint[]"`

	CreatedById   uint64 `json:"created_by_id" gorm:"column:created_by_id"`
	CreatedByName string `json:"created_by_name" gorm:"column:created_by_name;type:varchar(64)"`

	ActiveAt int64 `json:"active_at" gorm:"column:active_at"`
	Deadline int64 `json:"deadline" gorm:"column:deadline"`

	LoginAt int64 `json:"login_at" gorm:"-"`
}

func (u *User) CheckStatus(mustOk bool) error {
	switch u.Status {
	case StatusNormal:
	case StatusFrozen:
		if mustOk {
			return errors.New("用户被冻结")
		}
	default:
		return errors.New("用户状态未知")
	}

	return nil
}

func (u *User) IsValid(mustOk bool) error {
	now := time.Now()

	if now.UnixMilli() >= u.Deadline {
		return errors.New("用户已过期")
	}

	if now.UnixMilli() < u.ActiveAt {
		return errors.New("用户未启用")
	}

	if u.DeletedAt > 0 {
		return errors.New("用户不存在")
	}

	return u.CheckStatus(mustOk)
}

func (u *User) JwtEncode() (token string, err error) {

	now := time.Now()

	jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS512, jwt.MapClaims{
		"id":       u.Id,
		"username": u.Username,
		"status":   u.Status,
		"deadline": u.Deadline,
		"login_at": now.UnixMilli(),
	})

	if token, err = jwtToken.SignedString([]byte(opt.JwtTokenSecret)); err != nil {
		err = fmt.Errorf("JwtEncode: jwt token signed secret err: %v", err)
		logrus.Error(err)
		return "", nil
	}

	return
}

func (u *User) FromJwt(token string) *User {
	var (
		ok     bool
		err    error
		pt     *jwt.Token
		claims jwt.MapClaims
	)

	token = strings.TrimPrefix(token, "Bearer ")

	if pt, err = jwt.Parse(token, func(t *jwt.Token) (interface{}, error) {
		if _, ok = t.Method.(*jwt.SigningMethodHMAC); !ok {
			return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
		}

		return []byte(opt.JwtTokenSecret), nil
	}); err != nil {
		logrus.Errorf("jwt parse err: %v", err)
		return nil
	}

	if !pt.Valid {
		logrus.Warn("parsed jwt invalid")
		return nil
	}

	if claims, ok = pt.Claims.(jwt.MapClaims); !ok {
		logrus.Error("convert jwt claims err")
		return nil
	}

	u.Id = cast.ToUint64(claims["user_id"])
	u.Username = cast.ToString(claims["username"])
	u.Status = Status(cast.ToInt64(claims["status"]))
	u.Deadline = cast.ToInt64(claims["deadline"])
	u.LoginAt = cast.ToInt64(claims["login_at"])

	return u
}

func (u User) MarshalBinary() ([]byte, error) {
	return json.Marshal(map[string]any{
		"id":         u.Id,
		"created_at": u.CreatedAt,
		"updated_at": u.UpdatedAt,
		"deleted_at": u.DeletedAt,
		"username":   u.Username,
		"status":     u.Status.Value(),
		"nickname":   u.Nickname,
		"comment":    u.Comment,
		"role":       uint8(u.Role),
		"privileges": lo.Map(u.Privileges, func(item Privilege, index int) int64 {
			return item.Value()
		}),
		"created_by_id":   u.CreatedById,
		"created_by_name": u.CreatedByName,
		"active_at":       u.ActiveAt,
		"deadline":        u.Deadline,
		"login_at":        u.LoginAt,
	})
}