package services

import (
	"fmt"
	"time"

	"gitlink.org.cn/cloudream/common/pkgs/logger"

	"gitlink.org.cn/cloudream/jcs-pub/client/internal/db"
	"gitlink.org.cn/cloudream/jcs-pub/client/internal/downloader"
	"gitlink.org.cn/cloudream/jcs-pub/client/types"
	"gitlink.org.cn/cloudream/jcs-pub/common/models/datamap"
)

// PackageService 提供对包相关操作的服务接口
type PackageService struct {
	*Service
}

// PackageSvc 创建并返回一个PackageService的实例
func (svc *Service) PackageSvc() *PackageService {
	return &PackageService{Service: svc}
}

func (svc *PackageService) Get(packageID types.PackageID) (types.Package, error) {
	return svc.DB.Package().GetByID(svc.DB.DefCtx(), packageID)
}

func (svc *PackageService) GetByFullName(bucketName string, packageName string) (types.Package, error) {
	return svc.DB.Package().GetByFullName(svc.DB.DefCtx(), bucketName, packageName)
}

func (svc *PackageService) GetBucketPackages(bucketID types.BucketID) ([]types.Package, error) {
	return svc.DB.Package().GetBucketPackages(svc.DB.DefCtx(), bucketID)
}

func (svc *PackageService) Create(bucketID types.BucketID, name string) (types.Package, error) {
	pkg, err := svc.DB.Package().Create(svc.DB.DefCtx(), bucketID, name, time.Now())
	if err != nil {
		return types.Package{}, err
	}

	svc.EvtPub.Publish(&datamap.BodyNewPackage{
		Info: pkg,
	})

	return pkg, nil
}

func (svc *PackageService) DownloadPackage(packageID types.PackageID) (downloader.DownloadIterator, error) {
	// TODO 检查用户ID
	return svc.Downloader.DownloadPackage(packageID), nil
}

// DeletePackage 删除指定的包
func (svc *PackageService) DeletePackage(packageID types.PackageID) error {
	err := svc.DB.Package().DeleteComplete(svc.DB.DefCtx(), packageID)
	if err != nil {
		return err
	}

	svc.EvtPub.Publish(&datamap.BodyPackageDeleted{
		PackageID: packageID,
	})

	return nil
}

func (svc *PackageService) Clone(packageID types.PackageID, bucketID types.BucketID, name string) (types.Package, error) {
	var pkg types.Package
	var oldObjIDs []types.ObjectID
	var newObjIDs []types.ObjectID
	err := svc.DB.DoTx(func(tx db.SQLContext) error {
		var err error

		pkg, err = svc.DB.Package().Create(tx, bucketID, name, time.Now())
		if err != nil {
			return fmt.Errorf("creating package: %w", err)
		}

		objs, err := svc.DB.Object().GetPackageObjects(tx, packageID)
		if err != nil {
			return fmt.Errorf("getting package objects: %w", err)
		}

		objBlks, err := svc.DB.ObjectBlock().GetInPackageID(tx, packageID)
		if err != nil {
			return fmt.Errorf("getting object blocks: %w", err)
		}

		clonedObjs := make([]types.Object, len(objs))
		for i, obj := range objs {
			clonedObjs[i] = obj
			clonedObjs[i].ObjectID = 0
			clonedObjs[i].PackageID = pkg.PackageID
		}

		err = svc.DB.Object().BatchCreate(tx, &clonedObjs)
		if err != nil {
			return fmt.Errorf("batch creating objects: %w", err)
		}

		oldToNew := make(map[types.ObjectID]types.ObjectID)
		for i, obj := range clonedObjs {
			oldToNew[objs[i].ObjectID] = obj.ObjectID

			oldObjIDs = append(oldObjIDs, objs[i].ObjectID)
			newObjIDs = append(newObjIDs, obj.ObjectID)
		}

		clonedBlks := make([]types.ObjectBlock, len(objBlks))
		for i, blk := range objBlks {
			clonedBlks[i] = blk
			clonedBlks[i].ObjectID = oldToNew[blk.ObjectID]
		}

		err = svc.DB.ObjectBlock().BatchCreate(tx, clonedBlks)
		if err != nil {
			return fmt.Errorf("batch creating object blocks: %w", err)
		}

		return nil
	})
	if err != nil {
		return types.Package{}, err
	}

	svc.EvtPub.Publish(&datamap.BodyPackageCloned{
		SourcePackageID: packageID,
		NewPackage:      pkg,
		SourceObjectIDs: oldObjIDs,
		NewObjectIDs:    newObjIDs,
	})

	return pkg, nil
}

func (svc *PackageService) AddAccessStat(entries []db.AddAccessStatEntry) {
	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
	}

	err := svc.DB.DoTx(func(tx db.SQLContext) error {
		avaiPkgIDs, err := svc.DB.Package().BatchTestPackageID(tx, pkgIDs)
		if err != nil {
			return fmt.Errorf("batch test package id: %w", err)
		}

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

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

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

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

		return nil
	})

	if err != nil {
		logger.Warn(err.Error())
	}
}
