package db

import (
	"fmt"
	"time"

	"gitlink.org.cn/cloudream/jcs-pub/client/types"
	"gorm.io/gorm"
)

type PackageDB struct {
	*DB
}

func (db *DB) Package() *PackageDB {
	return &PackageDB{DB: db}
}

func (db *PackageDB) GetByID(ctx SQLContext, packageID types.PackageID) (types.Package, error) {
	var ret types.Package
	err := ctx.Table("Package").Where("PackageID = ?", packageID).First(&ret).Error
	return ret, err
}

func (db *PackageDB) GetByName(ctx SQLContext, bucketID types.BucketID, name string) (types.Package, error) {
	var ret types.Package
	err := ctx.Table("Package").Where("BucketID = ? AND Name = ?", bucketID, name).First(&ret).Error
	return ret, err
}

func (db *PackageDB) GetDetail(ctx SQLContext, packageID types.PackageID) (types.PackageDetail, error) {
	var pkg types.Package
	err := ctx.Table("Package").Where("PackageID = ?", packageID).First(&pkg).Error
	if err != nil {
		return types.PackageDetail{}, err
	}

	var ret struct {
		ObjectCount int64
		TotalSize   int64
	}

	err = ctx.Table("Object").
		Select("COUNT(*) as ObjectCount, SUM(Size) as TotalSize").
		Where("PackageID = ?", packageID).
		First(&ret).
		Error
	if err != nil {
		return types.PackageDetail{}, err
	}

	return types.PackageDetail{
		Package:     pkg,
		ObjectCount: ret.ObjectCount,
		TotalSize:   ret.TotalSize,
	}, nil
}

func (db *PackageDB) BatchGetDetailPaged(ctx SQLContext, lastPkgID types.PackageID, count int) ([]types.PackageDetail, error) {
	var pkgs []types.Package
	err := ctx.Table("Package").
		Where("PackageID > ?", lastPkgID).
		Order("PackageID ASC").
		Limit(count).
		Find(&pkgs).Error
	if err != nil {
		return nil, err
	}

	if len(pkgs) == 0 {
		return nil, nil
	}

	minPkgID := pkgs[0].PackageID
	maxPkgID := pkgs[len(pkgs)-1].PackageID

	type detail struct {
		ObjectCount int64
		TotalSize   int64
	}

	var details []detail
	err = ctx.
		Table("Package").
		Select("COUNT(ObjectID) as ObjectCount, SUM(Size) as TotalSize").
		Joins("left join Object o on Package.PackageID = o.PackageID").
		Where("Package.PackageID >= ? AND Package.PackageID <= ?", minPkgID, maxPkgID).
		Group("Package.PackageID").
		Order("Package.PackageID ASC").
		Find(&details).Error
	if err != nil {
		return nil, err
	}

	ret := make([]types.PackageDetail, len(pkgs))
	for i := range pkgs {
		ret[i] = types.PackageDetail{
			Package:     pkgs[i],
			ObjectCount: details[i].ObjectCount,
			TotalSize:   details[i].TotalSize,
		}
	}

	return ret, nil
}

func (db *PackageDB) BatchTestPackageID(ctx SQLContext, pkgIDs []types.PackageID) (map[types.PackageID]bool, error) {
	if len(pkgIDs) == 0 {
		return make(map[types.PackageID]bool), nil
	}

	var avaiIDs []types.PackageID
	err := ctx.Table("Package").
		Select("PackageID").
		Where("PackageID IN ?", pkgIDs).
		Find(&avaiIDs).Error
	if err != nil {
		return nil, err
	}

	avaiIDMap := make(map[types.PackageID]bool)
	for _, pkgID := range avaiIDs {
		avaiIDMap[pkgID] = true
	}

	return avaiIDMap, nil
}

func (*PackageDB) BatchGetAllPackageIDs(ctx SQLContext, start int, count int) ([]types.PackageID, error) {
	var ret []types.PackageID
	err := ctx.Table("Package").Select("PackageID").Limit(count).Offset(start).Find(&ret).Error
	return ret, err
}

func (db *PackageDB) GetBucketPackages(ctx SQLContext, bucketID types.BucketID) ([]types.Package, error) {
	var ret []types.Package
	err := ctx.Table("Package").
		Select("Package.*").
		Where("BucketID = ?", bucketID).
		Find(&ret).Error
	return ret, err
}

func (db *PackageDB) GetBucketPackagesByName(ctx SQLContext, bucketName string) ([]types.Package, error) {
	var ret []types.Package
	err := ctx.Table("Package").
		Select("Package.*").
		Joins("JOIN Bucket ON Package.BucketID = Bucket.BucketID").
		Where("Bucket.Name = ?", bucketName).
		Find(&ret).Error
	return ret, err
}

// 在指定名称的Bucket中查找指定名称的Package
func (*PackageDB) GetByFullName(ctx SQLContext, bucketName string, packageName string) (types.Package, error) {
	var ret types.Package
	err := ctx.Table("Package").
		Select("Package.*").
		Joins("JOIN Bucket ON Package.BucketID = Bucket.BucketID").
		Where("Package.Name = ? AND Bucket.Name = ?", packageName, bucketName).
		First(&ret).Error
	return ret, err
}

func (db *PackageDB) Create(ctx SQLContext, bucketID types.BucketID, name string, createTime time.Time) (types.Package, error) {
	var packageID int64
	err := ctx.Table("Package").
		Select("PackageID").
		Where("Name = ? AND BucketID = ?", name, bucketID).
		Scan(&packageID).Error

	if err != nil {
		return types.Package{}, err
	}
	if packageID != 0 {
		return types.Package{}, gorm.ErrDuplicatedKey
	}

	newPackage := types.Package{Name: name, BucketID: bucketID, CreateTime: createTime}
	if err := ctx.Create(&newPackage).Error; err != nil {
		return types.Package{}, fmt.Errorf("insert package failed, err: %w", err)
	}

	return newPackage, nil
}

func (*PackageDB) Delete(ctx SQLContext, packageID types.PackageID) error {
	err := ctx.Delete(&types.Package{}, "PackageID = ?", packageID).Error
	return err
}

// 删除与Package相关的所有数据
func (db *PackageDB) DeleteComplete(ctx SQLContext, packageID types.PackageID) error {
	if err := db.Package().Delete(ctx, packageID); err != nil {
		return fmt.Errorf("delete package state: %w", err)
	}

	if err := db.ObjectAccessStat().DeleteInPackage(ctx, packageID); err != nil {
		return fmt.Errorf("delete from object access stat: %w", err)
	}

	if err := db.ObjectBlock().DeleteInPackage(ctx, packageID); err != nil {
		return fmt.Errorf("delete from object block failed, err: %w", err)
	}

	if err := db.PinnedObject().DeleteInPackage(ctx, packageID); err != nil {
		return fmt.Errorf("deleting pinned objects in package: %w", err)
	}

	if err := db.Object().DeleteInPackage(ctx, packageID); err != nil {
		return fmt.Errorf("deleting objects in package: %w", err)
	}

	if err := db.PackageAccessStat().DeleteByPackageID(ctx, packageID); err != nil {
		return fmt.Errorf("deleting package access stat: %w", err)
	}

	return nil
}

func (*PackageDB) ChangeState(ctx SQLContext, packageID types.PackageID, state string) error {
	err := ctx.Exec("UPDATE Package SET State = ? WHERE PackageID = ?", state, packageID).Error
	return err
}

// 返回ErrRecordNotFound表示没有找到指定名称的Bucket，nil表示找到了
func (*PackageDB) HasPackageIn(ctx SQLContext, bucketID types.BucketID) error {
	var pkg types.Package
	return ctx.Table("Package").Where("BucketID = ?", bucketID).First(&pkg).Error
}

func (*PackageDB) Move(ctx SQLContext, packageID types.PackageID, newBktID types.BucketID, newName string) error {
	err := ctx.Table("Package").Where("PackageID = ?", packageID).Update("BucketID", newBktID).Update("Name", newName).Error
	return err
}

type AddAccessStatEntry struct {
	ObjectID    types.ObjectID    `json:"objectID"`
	PackageID   types.PackageID   `json:"packageID"`
	UserSpaceID types.UserSpaceID `json:"userSpaceID"`
	Counter     float64           `json:"counter"`
}

func (db *PackageDB) BatchAddPackageAccessStat(ctx SQLContext, entries []AddAccessStatEntry) error {
	pkgIDs := make([]types.PackageID, len(entries))
	objIDs := make([]types.ObjectID, len(entries))
	for i, e := range entries {
		pkgIDs[i] = e.PackageID
		objIDs[i] = e.ObjectID
	}

	avaiPkgIDs, err := db.Package().BatchTestPackageID(ctx, pkgIDs)
	if err != nil {
		return fmt.Errorf("batch test package id: %w", err)
	}

	avaiObjIDs, err := db.Object().BatchTestObjectID(ctx, objIDs)
	if err != nil {
		return fmt.Errorf("batch test object id: %w", err)
	}

	var willAdds []AddAccessStatEntry
	for _, e := range entries {
		if avaiPkgIDs[e.PackageID] && avaiObjIDs[e.ObjectID] {
			willAdds = append(willAdds, e)
		}
	}

	if len(willAdds) > 0 {
		err := db.PackageAccessStat().BatchAddCounter(ctx, willAdds)
		if err != nil {
			return fmt.Errorf("batch add package access stat counter: %w", err)
		}

		err = db.ObjectAccessStat().BatchAddCounter(ctx, willAdds)
		if err != nil {
			return fmt.Errorf("batch add object access stat counter: %w", err)
		}
	}

	return nil
}

// 尝试创建指定名称的Bucket和Package，如果Bucket不存在，则创建Bucket，如果Package已存在，则直接返回已有的Package
func (db *PackageDB) TryCreateAll(ctx SQLContext, bktName string, pkgName string) (types.Package, error) {
	bkt, err := db.Bucket().GetByName(ctx, bktName)
	if err == gorm.ErrRecordNotFound {
		bkt, err = db.Bucket().Create(ctx, bktName, time.Now())
		if err != nil {
			return types.Package{}, fmt.Errorf("create bucket: %w", err)
		}
	} else if err != nil {
		return types.Package{}, fmt.Errorf("get bucket by name: %w", err)
	}

	pkg, err := db.GetByName(ctx, bkt.BucketID, pkgName)
	if err == nil {
		return pkg, nil
	}
	if err != gorm.ErrRecordNotFound {
		return types.Package{}, fmt.Errorf("get package by name: %w", err)
	}

	pkg, err = db.Create(ctx, bkt.BucketID, pkgName, time.Now())
	if err != nil {
		return types.Package{}, fmt.Errorf("create package: %w", err)
	}

	return pkg, nil
}
