package dbutil

import (
	"context"
	"fmt"
	"time"

	"github.com/pingcap/errors"
	"github.com/tidbcloud/core-portal/internal/central/config"
	entity "github.com/tidbcloud/core-svc/model"
	"github.com/tidbcloud/pkgv2/log"
	"go.uber.org/zap"
	"gorm.io/gorm"
)

type TenantGroup struct {
	Tenant *entity.Tenant
	User   *entity.User
}

func GetTenantByID(tx *gorm.DB, tenantID entity.ID) (entity.Tenant, error) {
	tenant := entity.Tenant{}
	err := entity.NewTenantQuerySet(tx.Preload("Quotas")).IDEq(tenantID).One(&tenant)
	return tenant, TagNotFound(err)
}

type UserFilter struct {
	TenantID *entity.ID
	Email    *string
	Statuses []entity.UserStatus
	Roles    []entity.UserRole
}

func GetUserByID(tx *gorm.DB, orgID, userID entity.ID) (entity.User, error) {
	user := entity.User{}
	err := entity.NewUserQuerySet(tx.Preload("Projects")).TenantIDEq(orgID).IDEq(userID).One(&user)
	return user, TagNotFound(err)
}

func GetUserByUID(tx *gorm.DB, userID entity.ID) (*entity.User, error) {

	user := entity.User{}
	err := entity.NewUserQuerySet(tx.Preload("Projects")).IDEq(userID).One(&user)
	return &user, TagNotFound(err)
}

func GetUserByEmail(tx *gorm.DB, email string) (entity.User, error) {
	user := entity.User{}
	err := entity.NewUserQuerySet(tx.Preload("Projects")).EmailEq(email).PreloadTenant().One(&user)
	return user, TagNotFound(err)
}

type CreateUserRequired struct {
	Email     string
	FirstName string
	LastName  string
	InviteAt  *time.Time
	Status    entity.UserStatus
	Role      entity.UserRole
	TenantID  entity.ID
	Projects  []entity.Project
}

func TenantSystemUserEmail(tenantID entity.ID) string {
	return fmt.Sprintf("%d-%s", tenantID, config.SystemEmail)
}

// SystemUser gets or creates a system user
// TODO(Louis): add log context parameter
func SystemUser(tx *gorm.DB, tenantID entity.ID) (entity.User, error) {
	su, err := GetUserByEmail(tx, TenantSystemUserEmail(tenantID))
	if err == nil {
		return su, nil
	} else if nil == errors.Find(err, func(e error) bool { return e == gorm.ErrRecordNotFound }) {
		return su, err
	}
	log.Info(context.TODO(), "seeding a system user")

	user := entity.User{
		Email:     TenantSystemUserEmail(tenantID),
		FirstName: "system",
		LastName:  "user",
		Status:    entity.UserStatusActive,
		TenantID:  tenantID,
	}

	err = user.Create(tx)
	if err != nil {
		return user, errors.Annotate(err, "error while seeding system user")
	}

	log.Info(context.TODO(), "system user created", zap.Uint64("userID", uint64(user.ID)))
	return user, nil
}

func GetActiveProjects(ctx context.Context, tx *gorm.DB, orgID entity.ID) ([]entity.Project, error) {
	var projects []entity.Project
	err := entity.NewProjectQuerySet(tx).TenantIDEq(orgID).StatusEq(entity.ProjectStatusActive).All(&projects)
	if err != nil {
		log.WithContextL(ctx).Error("failed to query active projects", zap.Uint64("tenantID", orgID.Uint64()))
		return nil, errors.Annotatef(err, "failed to query active projects of org %d", orgID.Uint64())
	}
	return projects, nil
}
