package store

import (
	"errors"
	"strings"

	"github.com/jmoiron/sqlx"

	"github.com/pingcap/test-infra/common/model"
	"github.com/pingcap/test-infra/services/tcms/db"
	"github.com/pingcap/test-infra/services/tcms/utils"
	"github.com/pingcap/test-infra/services/tcms/utils/sql"
)

type userService struct {
	db *sql.Database
}

func (s *userService) init(db *sql.Database) {
	s.db = db
}

func (s *userService) Exist(ctx utils.Context, email string) (bool, error) {
	var isExist bool
	err := s.db.Get(&isExist, "SELECT COUNT(*)>0 FROM users WHERE email=?", email)
	if err != nil {
		ctx.Log().With("userEmail", email).Errorf(err, "UserExist failed")
		return false, err
	}

	return isExist, nil
}

func (s *userService) AutoComplete(ctx utils.Context, email string, count int) ([]*model.User, error) {
	users := []*db.User{}
	err := s.db.Select(&users, "SELECT id, email, name, avatar FROM users WHERE email LIKE ? ORDER BY name LIMIT ?",
		sql.SanitizeLike(email)+"%",
		count,
	)
	if err != nil {
		ctx.Log().With("userEmail", email).Errorf(err, "AutoCompleteUser failed")
		return nil, err
	}

	return s.toModel(users), nil
}

func (s *userService) GetByEmail(ctx utils.Context, email string) (*model.User, error) {
	user := &db.User{}
	err := s.db.Get(user, "SELECT id, email, name, avatar FROM users WHERE email=?", email)
	if err != nil {
		if sql.IsErrNoRows(err) {
			return nil, nil
		}
		return nil, err
	}

	return s.toModel([]*db.User{user})[0], nil
}

func (s *userService) ListBasicInfo(ctx utils.Context, ids []model.ID, emails []string) ([]*model.User, error) {
	if len(ids) == 0 && len(emails) == 0 {
		return nil, errors.New("expect one of ids and emails")
	}

	users := []*db.User{}

	stmt := strings.Builder{}
	stmt.WriteString("SELECT id, email, name, avatar FROM users WHERE 1=1")
	if len(ids) != 0 {
		stmt.WriteString(" AND id IN (:ids)")
	} else if len(emails) != 0 {
		stmt.WriteString(" AND email IN (:emails)")
	}
	query, args, _ := sqlx.Named(stmt.String(), map[string]interface{}{
		"ids":    ids,
		"emails": emails,
	})
	query, args, _ = sqlx.In(query, args...)

	err := s.db.Select(&users, query, args...)
	if err != nil {
		ctx.Log().Errorf(err, "select users failed")
		return nil, err
	}

	return s.toModel(users), nil
}

func (s *userService) GetEmails(ctx utils.Context, ids []model.ID) (map[model.ID]string, error) {
	if len(ids) == 0 {
		return map[model.ID]string{}, nil
	}

	users := []db.User{}
	query, args, _ := sqlx.In("SELECT id, email FROM users WHERE id IN (?)", ids)
	err := s.db.Select(&users, query, args...)
	if err != nil {
		ctx.Log().Errorf(err, "BatchGetUserEmail failed, userIDs= %v", ids)
		return nil, err
	}

	id2email := make(map[model.ID]string, len(users))
	for _, u := range users {
		id2email[u.ID] = u.Email
	}

	return id2email, nil
}

func (s *userService) Get(ctx utils.Context, id model.ID) (*model.User, error) {
	user := &db.User{}
	err := s.db.Get(user, "SELECT * FROM users WHERE id=?", id)
	if err != nil {
		if sql.IsErrNoRows(err) {
			return nil, nil
		}
		ctx.Log().With("userID", id).Errorf(err, "GetUser failed")
		return nil, err
	}

	return s.toModel([]*db.User{user})[0], nil
}

func (s *userService) Create(ctx utils.Context, user *model.User) (*model.User, error) {
	r, err := s.db.Exec("INSERT IGNORE INTO users (email, name, avatar) VALUES (?, ?, ?)", user.Email, user.Name, user.Avatar)
	if err != nil {
		ctx.Log().Errorf(err, "CreateUser failed")
		return nil, err
	}

	id, err := r.LastInsertId()
	if err != nil {
		ctx.Log().Errorf(err, "get last insert id failed")
		return nil, err
	}
	user.ID = model.ID(id)

	return user, nil
}

func (s *userService) Update(ctx utils.Context, user *model.User) (*model.User, error) {
	_, err := s.db.Exec("UPDATE users SET name=?, avatar=? WHERE email=?", user.Name, user.Avatar, user.Email)
	if err != nil {
		ctx.Log().Errorf(err, "UpdateUser failed")
		return nil, err
	}

	return user, nil
}

func (s *userService) toModel(us []*db.User) []*model.User {
	users := make([]*model.User, len(us))
	for i, u := range us {
		users[i] = &model.User{
			ID:     u.ID,
			Email:  u.Email,
			Name:   u.Name,
			Avatar: u.Avatar,
		}
	}

	return users
}
