package admin

import (
	"fmt"

	"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 RegionDisk struct {
	Id       uint64
	Provider string
	Region   string
	Type     string
	Capacity uint32
}

func (m UserContext) AddRegionDisk(regionDisk *RegionDisk) (*RegionDisk, error) {
	if err := validateRegionDisk(regionDisk); err != nil {
		return nil, err
	}
	if m.tenantAndUser.ID == 0 {
		return nil, errcode.NewInvalidInputErr(errors.New("not a normal user"))
	}
	var (
		err          error
		provider     entity.Provider
		dbRegion     entity.Region
		dbRegionDisk entity.RegionDisk
		diskProfile  *entity.DiskProfile
	)
	err = db.Transaction(func(tx *gorm.DB) error {
		if err := entity.NewProviderQuerySet(tx).NameEq(config.Cloud(regionDisk.Provider)).One(&provider); err != nil {
			return err
		}
		if err := entity.NewRegionQuerySet(tx).ProviderIDEq(provider.ID).NameEq(regionDisk.Region).One(&dbRegion); err != nil {
			return err
		}
		//create disk profile
		diskProfile, err = getOrCreateDiskProfile(tx, regionDisk.Type)
		if err != nil {
			return err
		}
		dbRegionDisk = entity.RegionDisk{
			Name:          string(diskProfile.Type),
			DisplayName:   string(diskProfile.Type),
			Capacity:      regionDisk.Capacity,
			RegionID:      dbRegion.ID,
			DiskProfileID: diskProfile.ID,
		}
		if err := tx.Create(&dbRegionDisk).Error; err != nil {
			return errors.Annotate(err, "create region disk failed")
		}
		desc := fmt.Sprintf("add new region disk %s for provider %s capactity %d", regionDisk.Type, regionDisk.Provider, regionDisk.Capacity)
		return dbutil.AddAccessLogNow(tx, m.tenantAndUser.User.TenantID, m.tenantAndUser.Email, desc)
	})
	if err != nil {
		return nil, err
	}
	return &RegionDisk{
		Id:       dbRegionDisk.ID.Uint64(),
		Provider: provider.Name.String(),
		Region:   dbRegion.Name,
		Type:     string(diskProfile.Type),
		Capacity: dbRegionDisk.Capacity,
	}, nil
}

func (m UserContext) UpdateRegionDisk(regionDisk *RegionDisk) (*RegionDisk, error) {
	if err := validateRegionDisk(regionDisk); err != nil {
		return nil, err
	}
	if m.tenantAndUser.ID == 0 {
		return nil, errcode.NewInvalidInputErr(errors.New("not a normal user"))
	}

	var (
		err          error
		provider     entity.Provider
		dbRegion     entity.Region
		dbRegionDisk entity.RegionDisk
		diskProfile  *entity.DiskProfile
	)
	err = db.Transaction(func(tx *gorm.DB) error {
		if err := entity.NewRegionDiskQuerySet(tx).IDEq(entity.ID(regionDisk.Id)).One(&dbRegionDisk); err != nil {
			return err
		}
		if err := entity.NewProviderQuerySet(tx).NameEq(config.Cloud(regionDisk.Provider)).One(&provider); err != nil {
			return err
		}
		if err := entity.NewRegionQuerySet(tx).ProviderIDEq(provider.ID).NameEq(regionDisk.Region).One(&dbRegion); err != nil {
			return err
		}
		//create disk profile
		diskProfile, err = getOrCreateDiskProfile(tx, regionDisk.Type)
		if err != nil {
			return err
		}
		dbRegionDisk.Capacity = regionDisk.Capacity
		if err = dbRegionDisk.Update(tx, entity.RegionDiskDBSchema.Capacity, entity.RegionDiskDBSchema.DiskProfileID); err != nil {
			return err
		}
		desc := fmt.Sprintf("update region disk %s for provider %s capactity %d", regionDisk.Type, regionDisk.Provider, regionDisk.Capacity)
		return dbutil.AddAccessLogNow(tx, m.tenantAndUser.User.TenantID, m.tenantAndUser.Email, desc)
	})
	if err != nil {
		return nil, err
	}
	return &RegionDisk{
		Id:       dbRegionDisk.ID.Uint64(),
		Provider: provider.Name.String(),
		Region:   dbRegion.Name,
		Type:     string(diskProfile.Type),
		Capacity: dbRegionDisk.Capacity,
	}, nil
}

func (m UserContext) DeleteRegionDisk(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
		}

		var regionDisk entity.RegionDisk
		err = entity.NewRegionDiskQuerySet(tx).PreloadDiskProfile().IDEq(entity.ID(id)).One(&regionDisk)
		if err != nil {
			return err
		}
		if err = regionDisk.Delete(tx); err != nil {
			return err
		}
		desc := fmt.Sprintf("delete region disk %s for provider %s", regionDisk.DiskProfile.Type, provider.Name)
		return dbutil.AddAccessLogNow(tx, m.tenantAndUser.User.TenantID, m.tenantAndUser.Email, desc)
	})
}

func (m UserContext) ListRegionDisks(providerStr string) ([]*RegionDisk, error) {
	if m.tenantAndUser.ID == 0 {
		return nil, errors.New("not a normal user")
	}

	var (
		provider      entity.Provider
		dbRegions     []entity.Region
		dbRegionDisks []entity.RegionDisk
		diskProfiles  []entity.DiskProfile
	)
	err := db.Transaction(func(tx *gorm.DB) error {
		if err := entity.NewProviderQuerySet(tx).NameEq(config.Cloud(providerStr)).One(&provider); err != nil {
			return err
		}
		if err := entity.NewDiskProfileQuerySet(tx).All(&diskProfiles); err != nil {
			return err
		}
		if err := entity.NewRegionQuerySet(tx).ProviderIDEq(provider.ID).All(&dbRegions); err != nil {
			return err
		}
		if len(dbRegions) == 0 {
			return nil
		}
		var ids []entity.ID
		for _, item := range dbRegions {
			ids = append(ids, item.ID)
		}
		return entity.NewRegionDiskQuerySet(tx).RegionIDIn(ids...).All(&dbRegionDisks)
	})
	if err != nil {
		return nil, err
	}

	regionIDMap := make(map[entity.ID]entity.Region)
	for _, item := range dbRegions {
		regionIDMap[item.ID] = item
	}
	diskProfileMap := make(map[entity.ID]entity.DiskProfile)
	for _, item := range diskProfiles {
		diskProfileMap[item.ID] = item
	}
	var regionDisks []*RegionDisk
	for _, item := range dbRegionDisks {
		regionDisks = append(regionDisks, &RegionDisk{
			Id:       item.ID.Uint64(),
			Provider: provider.Name.String(),
			Region:   regionIDMap[item.RegionID].Name,
			Type:     string(diskProfileMap[item.DiskProfileID].Type),
			Capacity: item.Capacity,
		})
	}
	return regionDisks, nil
}

func (m UserContext) GetRegionDisk(providerStr string, id uint64) (*RegionDisk, error) {
	if m.tenantAndUser.ID == 0 {
		return nil, errors.New("not a normal user")
	}

	var (
		provider     entity.Provider
		dbRegionDisk entity.RegionDisk
	)
	err := db.Transaction(func(tx *gorm.DB) error {
		if err := entity.NewProviderQuerySet(tx).NameEq(config.Cloud(providerStr)).One(&provider); err != nil {
			return err
		}
		return entity.NewRegionDiskQuerySet(tx).PreloadRegion().PreloadDiskProfile().IDEq(entity.ID(id)).One(&dbRegionDisk)
	})
	if err != nil {
		return nil, err
	}

	return &RegionDisk{
		Id:       dbRegionDisk.ID.Uint64(),
		Provider: provider.Name.String(),
		Region:   dbRegionDisk.Region.Name,
		Type:     string(dbRegionDisk.DiskProfile.Type),
		Capacity: dbRegionDisk.Capacity,
	}, nil
}

func validateRegionDisk(regionDisk *RegionDisk) error {
	if regionDisk.Type == "" {
		return errcode.NewInvalidInputErr(errors.Errorf("invalid region disk type %s", regionDisk.Type))
	}
	if regionDisk.Capacity == 0 {
		return errcode.NewInvalidInputErr(errors.New("invalid disk size"))
	}
	return nil
}
