package crdt

import (
	"sort"
	"strings"

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

const (
	versionsAddAttr = "S3-CRDT-Versions-Add"
)

type ObjectVersions struct {
	name     string
	objects  []*ObjectVersion
	addList  []string
	isSorted bool
}

type ObjectVersion struct {
	OjbID         oid.ID
	Headers       map[string]string
	CreationEpoch uint64
}

func (o *ObjectVersion) VersionID() string {
	return o.OjbID.EncodeToString()
}

func NewObjectVersions(name string) *ObjectVersions {
	return &ObjectVersions{name: name}
}

func NewObjectVersion(obj *object.Object) *ObjectVersion {
	objID, _ := obj.ID()
	headers := make(map[string]string)

	for _, attr := range obj.Attributes() {
		headers[attr.Key()] = attr.Value()
	}

	return &ObjectVersion{
		OjbID:         objID,
		Headers:       headers,
		CreationEpoch: obj.CreationEpoch(),
	}
}

func (v *ObjectVersions) Name() string {
	return v.name
}

func (v *ObjectVersions) AppendVersion(ov *ObjectVersion) {
	addVers := append(splitVersions(ov.Headers[versionsAddAttr]), ov.VersionID())
	v.objects = append(v.objects, ov)
	for _, add := range addVers {
		if !contains(v.addList, add) {
			v.addList = append(v.addList, add)
		}
	}
	v.isSorted = false
}

func (v *ObjectVersions) GetCRDTHeaders() map[string]string {
	if len(v.objects) == 0 {
		return nil
	}

	headers := make(map[string]string, 2)

	if len(v.addList) != 0 {
		headers[versionsAddAttr] = v.getAddHeader()
	}

	return headers
}

func (v *ObjectVersions) GetLast() *ObjectVersion {
	if len(v.objects) == 0 {
		return nil
	}

	v.sort()
	return v.objects[len(v.objects)-1]
}

func splitVersions(header string) []string {
	if len(header) == 0 {
		return nil
	}

	return strings.Split(header, ",")
}

func (v *ObjectVersions) sort() {
	if !v.isSorted {
		sort.Slice(v.objects, func(i, j int) bool {
			return less(v.objects[i], v.objects[j])
		})
		v.isSorted = true
	}
}

func (v *ObjectVersions) getAddHeader() string {
	return strings.Join(v.addList, ",")
}

func less(ov1, ov2 *ObjectVersion) bool {
	if ov1.CreationEpoch == ov2.CreationEpoch {
		return ov1.VersionID() < ov2.VersionID()
	}
	return ov1.CreationEpoch < ov2.CreationEpoch
}

func contains(list []string, elem string) bool {
	for _, item := range list {
		if elem == item {
			return true
		}
	}
	return false
}