package admin

import (
	"fmt"

	"github.com/pingcap/dbaas/central/db"
	"github.com/pingcap/dbaas/central/db/dbutil"
	"github.com/pingcap/dbaas/central/db/entity"
	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"gorm.io/gorm"
)

type CentralUser struct {
	Id        uint64
	FirstName string
	LastName  string
	Email     string
	Roles     []string
}

type CentralUserFilter struct {
	Email *string
}

func (m UserContext) UpsertCentralUser(user *CentralUser) (*CentralUser, error) {
	if m.tenantAndUser.ID == 0 {
		return nil, errors.New("not a normal user")
	}

	dbUser, err := buildCentralUser(user)
	if err != nil {
		return nil, err
	}
	err = db.Transaction(func(tx *gorm.DB) error {
		var oldUser entity.User
		err = entity.NewUserQuerySet(tx).EmailEq(dbUser.Email).One(&oldUser)
		if err != nil && err != gorm.ErrRecordNotFound {
			return err
		}
		if oldUser.ID > 0 && oldUser.ID != dbUser.ID {
			return errcode.NewInvalidInputErr(fmt.Errorf("duplicate email address: %s", dbUser.Email))
		}
		if dbUser.ID > 0 {
			err := tx.Model(dbUser).Association("Roles").Replace(dbUser.Roles)
			if err != nil {
				return errors.Annotate(err, "update user role")
			}
			if err := dbUser.Update(tx, entity.UserDBSchema.FirstName, entity.UserDBSchema.LastName, entity.UserDBSchema.Email); err != nil {
				return errors.Annotate(err, "update user")
			}
			return dbutil.AddAccessLogNow(tx, m.tenantAndUser.TenantID, m.tenantAndUser.Email, fmt.Sprintf("update %s with roles %v", user.Email, user.Roles))
		}
		if err := tx.Create(dbUser).Error; err != nil {
			return errors.Annotate(err, "create central user")
		}
		return dbutil.AddAccessLogNow(tx, m.tenantAndUser.TenantID, m.tenantAndUser.Email, fmt.Sprintf("add new user %s with roles %v", user.Email, user.Roles))

	})
	if err != nil {
		return nil, err
	}
	user.Id = dbUser.ID.Uint64()
	return user, nil
}

func (m UserContext) DeleteCentralUser(id uint64) error {
	if m.tenantAndUser.ID == 0 {
		return errors.New("not a normal user")
	}
	return db.Transaction(func(tx *gorm.DB) error {
		var user entity.User
		err := entity.NewUserQuerySet(tx).IDEq(entity.ID(id)).TenantIDEq(entity.PingCAPTenantID).One(&user)
		if err != nil {
			return err
		}
		err = user.Delete(tx)
		if err != nil {
			return err
		}
		return dbutil.AddAccessLogNow(tx, m.tenantAndUser.User.TenantID, m.tenantAndUser.Email, fmt.Sprintf("delete user %s", user.Email))
	})
}

func (m UserContext) GetCentralUserByID(id uint64) (*CentralUser, error) {
	if m.tenantAndUser.ID == 0 {
		return nil, errors.New("not a normal user")
	}
	var user entity.User
	err := db.Transaction(func(tx *gorm.DB) error {
		return entity.NewUserQuerySet(tx).IDEq(entity.ID(id)).TenantIDEq(entity.PingCAPTenantID).One(&user)
	})
	if err != nil {
		return nil, err
	}
	return buildInternalCentralUser(user), nil
}

func (m UserContext) ListCentralUsers(filter CentralUserFilter, limit, offset int) ([]*CentralUser, error) {
	if m.tenantAndUser.ID == 0 {
		return nil, errors.New("not a normal user")
	}
	var users []entity.User
	err := db.Transaction(func(tx *gorm.DB) error {
		qs := entity.NewUserQuerySet(
			tx.Preload("Roles"),
		)
		if filter.Email != nil {
			qs = qs.EmailEq(*filter.Email)
		}
		return qs.TenantIDEq(entity.PingCAPTenantID).OrderAscByID().Limit(limit).Offset(offset).All(&users)
	})
	if err != nil {
		return nil, err
	}
	var internalUsers []*CentralUser
	for _, item := range users {
		internalUsers = append(internalUsers, buildInternalCentralUser(item))
	}
	return internalUsers, nil
}

func (m UserContext) GetCentralUsersCount() (int64, error) {
	var count int64 = 0

	err := db.Transaction(func(tx *gorm.DB) error {
		var err error
		count, err = entity.NewUserQuerySet(
			tx.Preload("Roles"),
		).TenantIDEq(entity.PingCAPTenantID).Count()

		return err
	})

	return count, err
}

func (m UserContext) MarkTenantAsDelete(tenantID uint64) error {
	return db.Transaction(func(tx *gorm.DB) error {
		return dbutil.UpdateTenantStatus(tx, entity.ID(tenantID), entity.TenantStatusDeleted)
	})
}

func validateCentralUser(user *CentralUser) error {
	if user.Email == "" {
		return errcode.NewInvalidInputErr(errors.Errorf("invalid email %s", user.Email))
	}
	return nil
}

func buildCentralUser(user *CentralUser) (*entity.User, error) {
	if err := validateCentralUser(user); err != nil {
		return nil, err
	}
	dbUsers := &entity.User{
		TenantID:  entity.PingCAPTenantID,
		FirstName: user.FirstName,
		LastName:  user.LastName,
		Email:     user.Email,
		Status:    entity.UserStatusActive,
	}
	dbUsers.ID = entity.ID(user.Id)
	for _, item := range user.Roles {
		var role entity.Role
		err := db.Transaction(func(tx *gorm.DB) error {
			return entity.NewRoleQuerySet(tx).NameEq(item).TenantIDEq(entity.PingCAPTenantID).One(&role)
		})
		if err != nil {
			return nil, err
		}
		dbUsers.Roles = append(dbUsers.Roles, role)
	}
	return dbUsers, nil
}

func buildInternalCentralUser(item entity.User) *CentralUser {
	var roles []string
	for _, item := range item.Roles {
		roles = append(roles, item.Name)
	}
	return &CentralUser{
		Id:        item.ID.Uint64(),
		FirstName: item.FirstName,
		LastName:  item.LastName,
		Email:     item.Email,
		Roles:     roles,
	}
}
