package types

import (
	"fmt"
	"time"

	"github.com/samber/lo"
	"gitlink.org.cn/cloudream/common/utils/sort2"
	cotypes "gitlink.org.cn/cloudream/jcs-pub/coordinator/types"
)

const (
	ObjectPathSeparator = "/"
)

type PackageID int64

type ObjectID int64

type BucketID int64

type UserSpaceID int64

type Bucket struct {
	BucketID   BucketID  `gorm:"column:BucketID; primaryKey; type:bigint; autoIncrement" json:"bucketID"`
	Name       string    `gorm:"column:Name; type:varchar(255); not null" json:"name"`
	CreateTime time.Time `gorm:"column:CreateTime; type:datetime; not null" json:"createTime"`
}

func (Bucket) TableName() string {
	return "Bucket"
}

type Package struct {
	PackageID  PackageID `gorm:"column:PackageID; primaryKey; type:bigint; autoIncrement" json:"packageID"`
	Name       string    `gorm:"column:Name; type:varchar(255); not null" json:"name"`
	BucketID   BucketID  `gorm:"column:BucketID; type:bigint; not null" json:"bucketID"`
	CreateTime time.Time `gorm:"column:CreateTime; type:datetime; not null" json:"createTime"`
}

func (Package) TableName() string {
	return "Package"
}

type Object struct {
	ObjectID   ObjectID   `json:"objectID"  gorm:"column:ObjectID; primaryKey; type:bigint; autoIncrement" `
	PackageID  PackageID  `json:"packageID" gorm:"column:PackageID; type:bigint; not null"`
	Path       string     `json:"path" gorm:"column:Path; type:varchar(1024); not null"`
	Size       int64      `json:"size,string" gorm:"column:Size; type:bigint; not null"`
	FileHash   FileHash   `json:"fileHash" gorm:"column:FileHash; type:char(68); not null"`
	Redundancy Redundancy `json:"redundancy" gorm:"column:Redundancy; type: json; serializer:union"`
	CreateTime time.Time  `json:"createTime" gorm:"column:CreateTime; type:datetime; not null"`
	UpdateTime time.Time  `json:"updateTime" gorm:"column:UpdateTime; type:datetime; not null"`
}

func (Object) TableName() string {
	return "Object"
}

type ObjectBlock struct {
	ObjectID ObjectID `gorm:"column:ObjectID; primaryKey; type:bigint" json:"objectID"`
	Index    int      `gorm:"column:Index; primaryKey; type:int" json:"index"`
	// 这个块应该在哪个空间中
	UserSpaceID UserSpaceID `gorm:"column:UserSpaceID; primaryKey; type:bigint" json:"userSpaceID"`
	FileHash    FileHash    `gorm:"column:FileHash; type:char(68); not null" json:"fileHash"`
	Size        int64       `gorm:"column:Size; type:bigint" json:"size"`
}

func (ObjectBlock) TableName() string {
	return "ObjectBlock"
}

type UserSpace struct {
	UserSpaceID UserSpaceID `gorm:"column:UserSpaceID; primaryKey; type:bigint" json:"userSpaceID"`
	// 用户空间名称
	Name string `gorm:"column:Name; type:varchar(255); not null" json:"name"`
	// 用户空间所在的存储节点
	StorageID cotypes.StorageID `gorm:"column:StorageID; type:bigint; not null" json:"storageID"`
	// 用户在指定存储节点的凭证信息，比如用户账户，AK/SK等
	Credential cotypes.StorageCredential `gorm:"column:Credential; type:json; not null; serializer:union" json:"credential"`
	// 用户空间的分片存储配置，如果为空，则表示不使用分片存储
	ShardStore *cotypes.ShardStoreUserConfig `gorm:"column:ShardStore; type:json; serializer:json" json:"shardStore"`
	// 用户空间信息的版本号，每一次更改都需要更新版本号
	Revision int64 `gorm:"column:Revision; type:bigint; not null" json:"revision"`
}

func (UserSpace) TableName() string {
	return "UserSpace"
}

func (s UserSpace) String() string {
	return fmt.Sprintf("%v[id=%v,storageID=%v,rev=%v]", s.Name, s.UserSpaceID, s.StorageID, s.Revision)
}

type PackageAccessStat struct {
	PackageID PackageID `gorm:"column:PackageID; primaryKey; type:bigint" json:"packageID"`
	// 发起读取（调度）的用户空间ID
	UserSpaceID UserSpaceID `gorm:"column:UserSpaceID; primaryKey; type:bigint" json:"storageID"`
	// 前一日的读取量的滑动平均值
	Amount float64 `gorm:"column:Amount; type:double" json:"amount"`
	// 当日的读取量
	Counter float64 `gorm:"column:Counter; type:double" json:"counter"`
}

func (PackageAccessStat) TableName() string {
	return "PackageAccessStat"
}

type ObjectAccessStat struct {
	ObjectID ObjectID `gorm:"column:ObjectID; primaryKey; type:bigint" json:"objectID"`
	// 发起读取（调度）的用户空间ID
	UserSpaceID UserSpaceID `gorm:"column:UserSpaceID; primaryKey; type:bigint" json:"userStorageID"`
	// 前一日的读取量的滑动平均值
	Amount float64 `gorm:"column:Amount; type:float; not null" json:"amount"`
	// 当日的读取量
	Counter float64 `gorm:"column:Counter; type:float; not null" json:"counter"`
}

func (ObjectAccessStat) TableName() string {
	return "ObjectAccessStat"
}

type PinnedObject struct {
	ObjectID    ObjectID    `gorm:"column:ObjectID; primaryKey; type:bigint" json:"objectID"`
	UserSpaceID UserSpaceID `gorm:"column:UserSpaceID; primaryKey; type:bigint" json:"userSpaceID"`
	CreateTime  time.Time   `gorm:"column:CreateTime; type:datetime; not null" json:"createTime"`
}

func (PinnedObject) TableName() string {
	return "PinnedObject"
}

type ObjectDetail struct {
	Object   Object        `json:"object"`
	PinnedAt []UserSpaceID `json:"pinnedAt"`
	Blocks   []ObjectBlock `json:"blocks"`
}

func NewObjectDetail(object Object, pinnedAt []UserSpaceID, blocks []ObjectBlock) ObjectDetail {
	return ObjectDetail{
		Object:   object,
		PinnedAt: pinnedAt,
		Blocks:   blocks,
	}
}

func DetailsFromObjects(objects []Object) []ObjectDetail {
	details := make([]ObjectDetail, len(objects))
	for i, object := range objects {
		details[i] = ObjectDetail{
			Object: object,
		}
	}
	return details
}

// 将blocks放到对应的object中。要求objs和blocks都按ObjectID升序
func DetailsFillObjectBlocks(objs []ObjectDetail, blocks []ObjectBlock) {
	blksCur := 0
	for i := range objs {
		obj := &objs[i]
		// 1. 查询Object和ObjectBlock时均按照ObjectID升序排序
		// 2. ObjectBlock结果集中的不同ObjectID数只会比Object结果集的少
		// 因此在两个结果集上同时从头开始遍历时，如果两边的ObjectID字段不同，那么一定是ObjectBlock这边的ObjectID > Object的ObjectID，
		// 此时让Object的遍历游标前进，直到两边的ObjectID再次相等
		for ; blksCur < len(blocks); blksCur++ {
			if blocks[blksCur].ObjectID != obj.Object.ObjectID {
				break
			}
			obj.Blocks = append(obj.Blocks, blocks[blksCur])
		}
	}
}

// 将pinnedAt放到对应的object中。要求objs和pinnedAt都按ObjectID升序
func DetailsFillPinnedAt(objs []ObjectDetail, pinnedAt []PinnedObject) {
	pinnedCur := 0
	for i := range objs {
		obj := &objs[i]
		for ; pinnedCur < len(pinnedAt); pinnedCur++ {
			if pinnedAt[pinnedCur].ObjectID != obj.Object.ObjectID {
				break
			}
			obj.PinnedAt = append(obj.PinnedAt, pinnedAt[pinnedCur].UserSpaceID)
		}
	}
}

type GrouppedObjectBlock struct {
	ObjectID     ObjectID
	Index        int
	FileHash     FileHash
	Size         int64
	UserSpaceIDs []UserSpaceID
}

func (o *ObjectDetail) GroupBlocks() []GrouppedObjectBlock {
	grps := make(map[int]GrouppedObjectBlock)
	for _, block := range o.Blocks {
		grp, ok := grps[block.Index]
		if !ok {
			grp = GrouppedObjectBlock{
				ObjectID: block.ObjectID,
				Index:    block.Index,
				FileHash: block.FileHash,
				Size:     block.Size,
			}
		}
		grp.UserSpaceIDs = append(grp.UserSpaceIDs, block.UserSpaceID)
		grps[block.Index] = grp
	}

	return sort2.Sort(lo.Values(grps), func(l, r GrouppedObjectBlock) int { return l.Index - r.Index })
}

type UserSpaceDetail struct {
	UserID    cotypes.UserID
	UserSpace UserSpace
	Storage   cotypes.Storage
	MasterHub *cotypes.Hub
}

func (d UserSpaceDetail) String() string {
	return d.UserSpace.String()
}

type PackageDetail struct {
	Package     Package
	ObjectCount int64
	TotalSize   int64
}

type SpaceToSpaceResult struct {
	Success []string `json:"success"`
	Failed  []string `json:"failed"`
}
