package data

import (
	"strconv"
	"time"

	cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
	oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
)

const (
	UnversionedObjectVersionID = "null"
)

// NodeVersion represent node from tree service.
type NodeVersion struct {
	BaseNodeVersion
	IsUnversioned bool
	IsCombined    bool
}

// ExtendedNodeVersion contains additional node info to be able to sort versions by timestamp.
type ExtendedNodeVersion struct {
	NodeVersion *NodeVersion
	IsLatest    bool
	DirName     string
}

func (e ExtendedNodeVersion) Version() string {
	if e.NodeVersion.IsUnversioned {
		return UnversionedObjectVersionID
	}

	return e.NodeVersion.OID.EncodeToString()
}

func (e ExtendedNodeVersion) Name() string {
	if e.DirName != "" {
		return e.DirName
	}

	return e.NodeVersion.FilePath
}

// ExtendedObjectInfo contains additional node info to be able to sort versions by timestamp.
type ExtendedObjectInfo struct {
	ObjectInfo  *ObjectInfo
	NodeVersion *NodeVersion
	IsLatest    bool
}

func (e ExtendedObjectInfo) Version() string {
	if e.NodeVersion.IsUnversioned {
		return UnversionedObjectVersionID
	}

	return e.ObjectInfo.ID.EncodeToString()
}

// BaseNodeVersion is minimal node info from tree service.
// Basically used for "system" object.
type BaseNodeVersion struct {
	ID             uint64
	ParenID        uint64
	OID            oid.ID
	Timestamp      uint64
	Size           uint64
	ETag           string
	MD5            string
	FilePath       string
	Created        *time.Time
	Owner          *user.ID
	IsDeleteMarker bool
}

func (v *BaseNodeVersion) GetETag(md5Enabled bool) string {
	if md5Enabled && len(v.MD5) > 0 {
		return v.MD5
	}
	return v.ETag
}

// IsFilledExtra returns true is node was created by version of gate v0.29.x and later.
func (v BaseNodeVersion) IsFilledExtra() bool {
	return v.Created != nil && v.Owner != nil
}

func (v *BaseNodeVersion) FillExtra(owner *user.ID, created *time.Time, realSize uint64) {
	v.Owner = owner
	v.Created = created
	v.Size = realSize
}

type ObjectTaggingInfo struct {
	CnrID     cid.ID
	ObjName   string
	VersionID string
}

// MultipartInfo is multipart upload information.
type MultipartInfo struct {
	// ID is node id in tree service.
	// It's ignored when creating a new multipart upload.
	ID            uint64
	Key           string
	UploadID      string
	Owner         user.ID
	Created       time.Time
	Meta          map[string]string
	CopiesNumbers []uint32
	Finished      bool
}

// PartInfo is upload information about part.
type PartInfo struct {
	Key      string    `json:"key"`
	UploadID string    `json:"uploadId"`
	Number   int       `json:"number"`
	OID      oid.ID    `json:"oid"`
	Size     uint64    `json:"size"`
	ETag     string    `json:"etag"`
	MD5      string    `json:"md5"`
	Created  time.Time `json:"created"`
}

// ToHeaderString form short part representation to use in S3-Completed-Parts header.
func (p *PartInfo) ToHeaderString() string {
	// ETag value contains SHA256 checksum which is used while getting object parts attributes.
	return strconv.Itoa(p.Number) + "-" + strconv.FormatUint(p.Size, 10) + "-" + p.ETag
}

func (p *PartInfo) GetETag(md5Enabled bool) string {
	if md5Enabled && len(p.MD5) > 0 {
		return p.MD5
	}
	return p.ETag
}

// LockInfo is lock information to create appropriate tree node.
type LockInfo struct {
	id uint64

	legalHoldOID oid.ID
	setLegalHold bool

	retentionOID oid.ID
	setRetention bool
	untilDate    string
	isCompliance bool
}

func NewLockInfo(id uint64) *LockInfo {
	return &LockInfo{id: id}
}

func (l LockInfo) ID() uint64 {
	return l.id
}

func (l *LockInfo) SetLegalHold(objID oid.ID) {
	l.legalHoldOID = objID
	l.setLegalHold = true
}

func (l *LockInfo) ResetLegalHold() {
	l.setLegalHold = false
}

func (l LockInfo) LegalHold() oid.ID {
	return l.legalHoldOID
}

func (l LockInfo) IsLegalHoldSet() bool {
	return l.setLegalHold
}

func (l *LockInfo) SetRetention(objID oid.ID, until string, isCompliance bool) {
	l.retentionOID = objID
	l.setRetention = true
	l.untilDate = until
	l.isCompliance = isCompliance
}

func (l LockInfo) IsRetentionSet() bool {
	return l.setRetention
}

func (l LockInfo) Retention() oid.ID {
	return l.retentionOID
}

func (l LockInfo) UntilDate() string {
	return l.untilDate
}

func (l LockInfo) IsCompliance() bool {
	return l.isCompliance
}