package admin

import (
	"fmt"

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

type MachineProfile struct {
	Id           uint64
	Provider     string
	InstanceType string
	Cpu          uint32
	MemoryGi     uint32
}

func (m UserContext) UpsertMachineProfile(machine *MachineProfile) (entity.ID, error) {
	if err := validateMachine(machine); err != nil {
		return 0, err
	}
	if m.tenantAndUser.ID == 0 {
		return 0, errors.New("not a normal user")
	}

	var dbMachineProfile entity.MachineProfile
	err := db.Transaction(func(tx *gorm.DB) error {
		provider, err := dbutil.GetProvider(tx, config.Cloud(machine.Provider))
		if err != nil {
			return err
		}

		dbMachineProfile = buildMachineProfile(machine, provider)
		if dbMachineProfile.ID > 0 {
			err = dbMachineProfile.Update(tx, entity.MachineProfileDBSchema.Memory, entity.MachineProfileDBSchema.CPU, entity.MachineProfileDBSchema.InstanceType)
			if err != nil {
				return err
			}
			return dbutil.AddAccessLogNow(tx,
				m.tenantAndUser.User.TenantID,
				m.tenantAndUser.Email,
				fmt.Sprintf("update machine %s for provider %s", machine.InstanceType, machine.Provider),
			)
		}

		err = dbMachineProfile.Create(tx)
		if err != nil {
			return err
		}
		return dbutil.AddAccessLogNow(tx,
			m.tenantAndUser.User.TenantID,
			m.tenantAndUser.Email,
			fmt.Sprintf("add new machine %s for provider %s", machine.InstanceType, machine.Provider),
		)

	})

	return dbMachineProfile.ID, err
}

// nolint
func validateMachine(machine *MachineProfile) error {
	if machine.MemoryGi == 0 {
		return errcode.NewInvalidInputErr(errors.New("invalid memory"))
	}
	if machine.Cpu == 0 {
		return errcode.NewInvalidInputErr(errors.New("invalid cpu"))
	}
	return nil
}

func (m UserContext) ListMachines(providerStr string, limit, offset int) ([]*MachineProfile, error) {

	if m.tenantAndUser.ID == 0 {
		return nil, errors.New("not a normal user")
	}

	var provider entity.Provider
	var dbMachineProfiles []entity.MachineProfile
	err := db.Transaction(func(tx *gorm.DB) error {
		var err error
		provider, err = dbutil.GetProvider(tx, config.Cloud(providerStr))
		if err != nil {
			return err
		}
		return entity.NewMachineProfileQuerySet(tx).ProviderIDEq(
			provider.ID,
		).OrderAscByID().Limit(limit).Offset(offset).All(&dbMachineProfiles)
	})
	if err != nil {
		return nil, err
	}
	var machines []*MachineProfile
	for _, item := range dbMachineProfiles {
		machines = append(machines, buildInternalMachineProfile(item, provider))
	}
	return machines, nil
}

func (m UserContext) GetListMachinesCount(providerName string) (int64, error) {
	var count int64

	err := db.Transaction(func(tx *gorm.DB) error {
		provider, err := dbutil.GetProvider(tx, config.Cloud(providerName))
		if err != nil {
			return err
		}
		count, err = entity.NewMachineProfileQuerySet(tx).ProviderIDEq(provider.ID).Count()
		return err
	})

	return count, err
}

func (m UserContext) DeleteMachine(providerId string, id uint64) error {

	if m.tenantAndUser.ID == 0 {
		return errors.New("not a normal user")
	}

	return db.Transaction(func(tx *gorm.DB) error {

		provider, err := dbutil.GetProvider(tx, config.Cloud(providerId))
		if err != nil {
			return err
		}
		machine, err := dbutil.GetMachineProfileByID(tx, entity.ID(id))
		if err != nil {
			return err
		}
		if machine.ProviderID != provider.ID {
			return errors.New("invalid provider")
		}
		if err := machine.Delete(tx); err != nil {
			return err
		}
		return dbutil.AddAccessLogNow(tx, m.tenantAndUser.TenantID, m.tenantAndUser.Email, fmt.Sprintf("delete machine %s for provider %s", machine.InstanceType, provider.Name))
	})

}

func (m UserContext) GetMachineProfileByID(providerId string, id uint64) (*MachineProfile, error) {
	if m.tenantAndUser.ID == 0 {
		return nil, errors.New("not a normal user")
	}
	var machineProfile *entity.MachineProfile
	var provider entity.Provider
	err := db.Transaction(func(tx *gorm.DB) error {
		var err error
		provider, err = dbutil.GetProvider(tx, config.Cloud(providerId))
		if err != nil {
			return err
		}
		machineProfile, err = dbutil.GetMachineProfileByID(tx, entity.ID(id))
		if err != nil {
			return err
		}
		if machineProfile.ProviderID != provider.ID {
			return errors.New("invalid provider")
		}
		return nil
	})
	if err != nil {
		return nil, err
	}
	return buildInternalMachineProfile(*machineProfile, provider), nil
}

func buildMachineProfile(machine *MachineProfile, provider entity.Provider) entity.MachineProfile {
	dbMachine := entity.MachineProfile{
		InstanceType: capacity.MachineType(machine.InstanceType),
		CPU:          machine.Cpu,
		Memory:       machine.MemoryGi,
		ProviderID:   provider.ID,
		MachineArch:  entity.MachineArchX86,
	}
	dbMachine.ID = entity.ID(machine.Id)
	return dbMachine
}

func buildInternalMachineProfile(machineProfile entity.MachineProfile, provider entity.Provider) *MachineProfile {
	return &MachineProfile{
		Id:           machineProfile.ID.Uint64(),
		Provider:     provider.Name.String(),
		InstanceType: machineProfile.InstanceType.String(),
		Cpu:          machineProfile.CPU,
		MemoryGi:     machineProfile.Memory,
	}
}
