package models

import (
	"encoding/json"
	"errors"
	_ "github.com/go-sql-driver/mysql"
	"github.com/iwind/TeaGo/Tea"
	"github.com/iwind/TeaGo/dbs"
	"github.com/iwind/TeaGo/maps"
	"github.com/iwind/TeaGo/types"
	"time"
)

// TODO 定时清理过期的SESSION

type LoginSessionDAO dbs.DAO

func NewLoginSessionDAO() *LoginSessionDAO {
	return dbs.NewDAO(&LoginSessionDAO{
		DAOObject: dbs.DAOObject{
			DB:     Tea.Env,
			Table:  "edgeLoginSessions",
			Model:  new(LoginSession),
			PkName: "id",
		},
	}).(*LoginSessionDAO)
}

var SharedLoginSessionDAO *LoginSessionDAO

func init() {
	dbs.OnReady(func() {
		SharedLoginSessionDAO = NewLoginSessionDAO()
	})
}

// CreateSession 创建SESSION
func (this *LoginSessionDAO) CreateSession(tx *dbs.Tx, sid string, ip string, expiresAt int64) (int64, error) {
	if len(sid) == 0 || len(sid) > 64 {
		return 0, errors.New("invalid 'sid'")
	}

	// 是否已存在
	oldSessionId, err := this.Query(tx).
		Attr("sid", sid).
		ResultPk().
		FindInt64Col(0)
	if err != nil {
		return 0, err
	}

	var op = NewLoginSessionOperator()
	if oldSessionId > 0 {
		op.Id = oldSessionId
	}

	op.Sid = sid
	op.Ip = ip
	op.Values = "{}"
	op.ExpiresAt = expiresAt
	op.CreatedAt = time.Now().Unix()

	if oldSessionId > 0 {
		err := this.Save(tx, op)
		if err != nil {
			return 0, err
		}
		return oldSessionId, nil
	}
	return this.SaveInt64(tx, op)
}

// WriteSessionValue 向SESSION中写入数据
func (this *LoginSessionDAO) WriteSessionValue(tx *dbs.Tx, sid string, key string, value any) error {
	if len(sid) == 0 || len(sid) > 64 {
		return errors.New("invalid 'sid'")
	}

	// 是否存在
	sessionOne, err := this.Query(tx).
		Attr("sid", sid).
		Find()
	if err != nil {
		return err
	}
	var sessionId int64
	var isNewSession = false
	var valueMap = maps.Map{}
	if sessionOne != nil {
		var session = sessionOne.(*LoginSession)
		if session.IsAvailable() {
			sessionId = int64(session.Id)

			if !IsNull(session.Values) {
				err = json.Unmarshal(session.Values, &valueMap)
				if err != nil {
					return err
				}
			}
		} else {
			// 不可用则删除之
			err = this.Query(tx).
				Pk(session.Id).
				DeleteQuickly()
			if err != nil {
				return err
			}
		}
	}
	if sessionId == 0 {
		// 不存在，则创建之
		sessionId, err = this.CreateSession(tx, sid, "", time.Now().Unix()+30*86400 /** 默认30天**/)
		if err != nil {
			return err
		}
		isNewSession = true
	}

	var sessionOp = NewLoginSessionOperator()
	sessionOp.Id = sessionId

	// 获取用户ID
	var adminId int64
	var userId int64

	switch key {
	case "adminId":
		adminId = types.Int64(value)
	case "userId":
		userId = types.Int64(value)
	}

	if adminId > 0 || userId > 0 {
		sessionOp.AdminId = adminId
		sessionOp.UserId = userId

		if isNewSession {
			// 删除此用户之前创建的SESSION，防止单个用户SESSION过多
			// TODO 将来改成按照活跃时间排序
			const maxSessionsPerUser = 10
			oldOnes, err := this.Query(tx).
				ResultPk().
				Attr("adminId", adminId).
				Attr("userId", userId).
				Asc("createdAt").
				FindAll()
			if err != nil {
				return err
			}
			var countOldOnes = len(oldOnes)
			if countOldOnes > maxSessionsPerUser {
				var countDeleted int
				for _, oldOne := range oldOnes {
					var oldSessionId = int64(oldOne.(*LoginSession).Id)
					if oldSessionId == sessionId {
						continue
					}

					if countDeleted < countOldOnes-maxSessionsPerUser {
						err = this.Query(tx).
							Pk(oldSessionId).
							DeleteQuickly()
						if err != nil {
							return err
						}
						countDeleted++
					} else {
						break
					}
				}
			}
		}
	}

	// 写入数据
	valueMap[key] = value
	sessionOp.Values = valueMap.AsJSON()
	return this.Save(tx, sessionOp)
}

// DeleteSession 删除SESSION
func (this *LoginSessionDAO) DeleteSession(tx *dbs.Tx, sid string) error {
	return this.Query(tx).
		Attr("sid", sid).
		DeleteQuickly()
}

// FindSession 查询SESSION
func (this *LoginSessionDAO) FindSession(tx *dbs.Tx, sid string) (*LoginSession, error) {
	one, err := this.Query(tx).
		Attr("sid", sid).
		Find()
	if err != nil || one == nil {
		return nil, err
	}
	var session = one.(*LoginSession)

	// 不可用则删除
	if !session.IsAvailable() {
		err = this.Query(tx).
			Pk(session.Id).
			DeleteQuickly()
		if err != nil {
			return nil, err
		}
	}
	return session, nil
}
