package admin

import (
	"fmt"

	"github.com/pingcap/errcode"
	"github.com/pingcap/errors"
	"github.com/tidbcloud/central-sdk/db"
	"github.com/tidbcloud/central-sdk/db/dbutil"
	"github.com/tidbcloud/central-sdk/db/entity"
	"gorm.io/gorm"
)

type TenantUser struct {
	Id        uint64
	OrgId     uint64
	FirstName string
	LastName  string
	Email     string
	Status    string
	Roles     []string
	Role      string
}

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

	dbUser, err := buildTenantUser(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.Status, entity.UserDBSchema.Email, entity.UserDBSchema.Role); 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 for tenant %d", user.Email, user.Roles, user.OrgId))
		}
		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 for tenant %d", user.Email, user.Roles, user.OrgId))
	})
	if err != nil {
		return nil, err
	}
	user.Id = dbUser.ID.Uint64()
	return user, nil
}

func (m UserContext) DeleteTenantUser(tenantId, 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
		if err := entity.NewUserQuerySet(tx).IDEq(entity.ID(id)).TenantIDEq(entity.ID(tenantId)).IDEq(entity.ID(id)).One(&user); err != nil {
			return err
		}
		if err := user.Delete(tx); err != nil {
			return err
		}
		return dbutil.AddAccessLogNow(tx, m.tenantAndUser.User.TenantID, m.tenantAndUser.Email, fmt.Sprintf("delete user %s  for tenant %d", user.Email, tenantId))
	})
}

func (m UserContext) ListTenantUsers(tenantID uint64, limit, offset int) ([]*TenantUser, 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 {
		return entity.NewUserQuerySet(
			tx.Preload("Roles"),
		).TenantIDEq(entity.ID(tenantID)).OrderAscByID().Offset(offset).Limit(limit).All(&users)
	})
	if err != nil {
		return nil, err
	}
	var internalUsers []*TenantUser
	for _, item := range users {
		internalUsers = append(internalUsers, buildInternalTenantUser(item))
	}
	return internalUsers, nil
}

func (m UserContext) GetTenantUsersCount(tenantID uint64) (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.ID(tenantID)).Count()

		return err
	})

	return count, err
}

func (m UserContext) GetTenantUserByID(tenantId, id uint64) (*TenantUser, 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.ID(tenantId)).One(&user)
	})
	if err != nil {
		return nil, err
	}
	return buildInternalTenantUser(user), nil
}

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

func buildTenantUser(user *TenantUser) (*entity.User, error) {
	if err := validateTenantUser(user); err != nil {
		return nil, err
	}
	dbUsers := &entity.User{
		FirstName: user.FirstName,
		LastName:  user.LastName,
		Email:     user.Email,
		Status:    entity.UserStatus(user.Status),
		TenantID:  entity.ID(user.OrgId),
		Role:      entity.UserRole(user.Role),
	}
	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.ID(user.OrgId)).One(&role)
		})
		if err != nil {
			return nil, err
		}
		dbUsers.Roles = append(dbUsers.Roles, role)
	}
	return dbUsers, nil
}

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