package mysql

import (
	"context"
	"strings"

	"gorm.io/gorm"

	"github.com/pingcap-inc/pchub/internal/pkg/code"
	v1 "github.com/pingcap-inc/pchub/model/v1"
	"github.com/pingcap-inc/pchub/pkg/errors"
)

type user struct {
	db *gorm.DB
}

func newUser(ds *datastore) *user {
	return &user{db: ds.db}
}

func (u *user) Search(_ context.Context, kw string) ([]*v1.User, error) {
	var res []*v1.User

	kw = strings.ReplaceAll(kw, "'", "")
	kw = strings.ReplaceAll(kw, "%", "")

	d := u.db.
		Where("LOWER(name) like '%" + kw + "%' OR LOWER(enName) like '%" + kw + "%' OR email like '%" + kw + "%'").
		Order("name asc").
		Limit(20).
		Find(&res)

	return res, dbNotFoundErrorWrap(d.Error)
}

func (u *user) Create(_ context.Context, user *v1.User) (*v1.User, error) {
	return user, u.db.Create(user).Error
}

func (u *user) BatchInit(_ context.Context, users []*v1.User) error {
	tx := u.db.Begin()

	userLarkIDs := make([]string, 0, len(users))
	for _, u := range users {
		userLarkIDs = append(userLarkIDs, u.LarkUserID)
	}

	// delete all users
	err := tx.Unscoped().Where("larkUserID IN (?)", userLarkIDs).Delete(&v1.User{}).Error
	if err != nil {
		tx.Rollback()

		return err
	}

	// insert all users
	if len(users) > 0 {
		err := tx.Create(&users).Error
		if err != nil {
			tx.Rollback()

			return err
		}
	}

	tx.Commit()

	return nil
}

func (u *user) BatchSave(_ context.Context, news, updates []*v1.User) error {
	tx := u.db.Begin()

	// new
	if len(news) > 0 {
		err := tx.Create(news).Error
		if err != nil {
			tx.Rollback()

			return err
		}
	}

	// update
	for _, g := range updates {
		err := tx.Save(&g).Error
		if err != nil {
			tx.Rollback()

			return err
		}
	}

	tx.Commit()

	return nil
}

func (u *user) Update(_ context.Context, user *v1.User) error {
	return u.db.Save(user).Error
}

func (u *user) GetByAnyID(_ context.Context, anyID string) (*v1.User, error) {
	user := &v1.User{}
	err := u.db.Where("id = ? or authingID = ? or larkUserID = ?", anyID, anyID, anyID).First(&user).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.WithCode(code.ErrUserNotFound, "gorm.ErrRecordNotFound: %v", err.Error())
		}

		return nil, errors.WithCode(code.ErrDatabase, "gorm.ErrRecordNotFound: %v", err.Error())
	}

	return user, nil
}

func (u *user) GetByEmail(_ context.Context, email string) (*v1.User, error) {
	user := &v1.User{}
	err := u.db.Where("email = ?", email).First(&user).Error
	if err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, errors.WithCode(code.ErrUserNotFound, "gorm.ErrRecordNotFound: %v", err.Error())
		}

		return nil, errors.WithCode(code.ErrDatabase, "gorm.ErrRecordNotFound: %v", err.Error())
	}

	return user, nil
}

func (u *user) MGetByLarkIDs(_ context.Context, larkIDs []string) ([]*v1.User, error) {
	var res []*v1.User
	d := u.db.
		Where("larkUserID IN (?)", larkIDs).
		Order("id asc").
		Find(&res)

	return res, dbNotFoundErrorWrap(d.Error)
}

func (u *user) MDelByLarkIDsIfNotIn(_ context.Context, larkIDs []string) error {
	return u.db.Where("larkUserID NOT IN (?)", larkIDs).Delete(&v1.User{}).Error
}

func (u *user) Gets(_ context.Context) ([]*v1.User, error) {
	var res []*v1.User
	d := u.db.Find(&res)

	return res, dbNotFoundErrorWrap(d.Error)
}

func (u *user) GetByAwsPID(_ context.Context, PID string) (*v1.User, error) {
	var res *v1.User
	d := u.db.Where("awsPID = ?", PID).First(&res)

	return res, dbNotFoundErrorWrap(d.Error)
}
