package user

import (
	"context"
	"errors"
	"fmt"

	"github.com/tidbcloud/account/internel/dao/entity"
	"github.com/tidbcloud/tiinf/errs"
	"gorm.io/gorm"
)

const (
	userSessionTable = "user_sessions"
)

type UserSessionDaoIface interface {
	CreateUserSession(ctx context.Context, tx *gorm.DB, sess *entity.UserSession) error
	DeleteUserSessionBySessionID(ctx context.Context, tx *gorm.DB, sid string) error
	DeleteUserSessionByEmail(ctx context.Context, tx *gorm.DB, email string) error
	UpdateUserSessionBySessionID(ctx context.Context, tx *gorm.DB, sess *entity.UserSession) error
	UpdateUserSessionByEmail(ctx context.Context, tx *gorm.DB, sess *entity.UserSession) error
	BatchUpdateSessionExpiredAt(ctx context.Context, tx *gorm.DB, items []entity.SessionExpiredAtItem) error
	GetUserSessionBySessionID(ctx context.Context, tx *gorm.DB, sid string) (*entity.UserSession, error)
	ListUserSessionsByEmail(ctx context.Context, tx *gorm.DB, email string) ([]entity.UserSession, error)
	BatchListUserSessions(ctx context.Context, tx *gorm.DB, start uint64, limit int) ([]entity.UserSession, error)
}

type UserSessionDao struct {
}

func NewUserSessionDao() UserSessionDaoIface {
	return &UserSessionDao{}
}

func (dao *UserSessionDao) CreateUserSession(ctx context.Context, tx *gorm.DB, sess *entity.UserSession) error {
	err := tx.Table(userSessionTable).Save(sess).Error
	if err != nil {
		return errs.WrapGORMError(err, "CreateUserSession error")
	}
	return nil
}

func (dao *UserSessionDao) DeleteUserSessionBySessionID(ctx context.Context, tx *gorm.DB, sid string) error {
	res := &entity.UserSession{}
	err := tx.Table(userSessionTable).Where("session_id=?", sid).Delete(res).Error
	if err != nil {
		return errs.WrapGORMError(err, fmt.Sprintf("DeleteUserSessionBySessionID error: %s", sid))
	}
	return nil
}

func (dao *UserSessionDao) DeleteUserSessionByEmail(ctx context.Context, tx *gorm.DB, email string) error {
	var res []entity.UserSession
	err := tx.Table(userSessionTable).Where("email=?", email).Delete(&res).Error
	if err != nil {
		return errs.WrapGORMError(err, fmt.Sprintf("DeleteUserSessionByEmail error: %s", email))
	}
	return nil
}

func (dao *UserSessionDao) UpdateUserSessionBySessionID(ctx context.Context, tx *gorm.DB, sess *entity.UserSession) error {
	updateMap := map[string]interface{}{}

	if sess.ExpiredAt != nil {
		updateMap["expired_at"] = sess.ExpiredAt
	}
	if sess.RevokedAt != nil {
		updateMap["revoked_at"] = sess.RevokedAt
	}

	err := tx.Table(userSessionTable).Where("session_id=?", sess.SessionID).Updates(updateMap).Error
	if err != nil {
		return errs.WrapGORMError(err, fmt.Sprintf("UpdateUserSessionBySessionID error: %+v", sess))
	}
	return nil
}

func (dao *UserSessionDao) UpdateUserSessionByEmail(ctx context.Context, tx *gorm.DB, sess *entity.UserSession) error {
	updateMap := map[string]interface{}{}

	if sess.ExpiredAt != nil {
		updateMap["expired_at"] = sess.ExpiredAt
	}
	if sess.RevokedAt != nil {
		updateMap["revoked_at"] = sess.RevokedAt
	}

	err := tx.Table(userSessionTable).Where("email=?", sess.Email).Updates(updateMap).Error
	if err != nil {
		return errs.WrapGORMError(err, fmt.Sprintf("UpdateUserSessionByEmail error: %+v", sess))
	}
	return nil
}

func (dao *UserSessionDao) BatchUpdateSessionExpiredAt(ctx context.Context, tx *gorm.DB, items []entity.SessionExpiredAtItem) error {
	if len(items) == 0 {
		return nil
	}
	caseExp := "CASE session_id"
	ids := make([]string, 0, len(items))
	for _, it := range items {
		caseExp += fmt.Sprintf(" WHEN '%s' THEN '%s'", it.SessionID, it.ExpiredAt.Format("2006-01-02 15:04:05"))
		ids = append(ids, it.SessionID)
	}
	caseExp += " END"
	err := tx.Table(userSessionTable).
		Where("session_id IN ?", ids).
		Update("expired_at", gorm.Expr(caseExp)).Error
	if err != nil {
		return errs.WrapGORMError(err, fmt.Sprintf("BatchUpdateSessionExpiredAt error: %d items", len(items)))
	}
	return nil
}

func (dao *UserSessionDao) GetUserSessionBySessionID(ctx context.Context, tx *gorm.DB, sid string) (*entity.UserSession, error) {
	res := &entity.UserSession{}
	err := tx.Table(userSessionTable).Where("session_id=?", sid).First(res).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, nil
		}
		return nil, errs.WrapGORMError(err, fmt.Sprintf("GetUserSessionBySessionID error: %s", sid))
	}
	return res, nil
}

func (dao *UserSessionDao) ListUserSessionsByEmail(ctx context.Context, tx *gorm.DB, email string) ([]entity.UserSession, error) {
	var res []entity.UserSession
	err := tx.Table(userSessionTable).Where("email=?", email).Find(&res).Error
	if err != nil {
		return res, errs.WrapGORMError(err, fmt.Sprintf("ListUserSessionsByEmail error: %s", email))
	}
	return res, nil
}

func (dao *UserSessionDao) BatchListUserSessions(ctx context.Context, tx *gorm.DB, start uint64, limit int) ([]entity.UserSession, error) {
	var res []entity.UserSession
	err := tx.Table(userSessionTable).Where("id > ?", start).Order("id asc").Limit(limit).Find(&res).Error
	return res, err
}
