package layer

import (
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/nspcc-dev/neofs-api-go/pkg/object"
	"github.com/nspcc-dev/neofs-api-go/pkg/owner"
)

type (
	// ObjectInfo holds S3 object data.
	ObjectInfo struct {
		id    *object.ID
		isDir bool

		Bucket      string
		Name        string
		Size        int64
		ContentType string
		Created     time.Time
		Owner       *owner.ID
		Headers     map[string]string
	}

	// ListObjectsInfo - container for list objects.
	ListObjectsInfo struct {
		// Indicates whether the returned list objects response is truncated. A
		// value of true indicates that the list was truncated. The list can be truncated
		// if the number of objects exceeds the limit allowed or specified
		// by max keys.
		IsTruncated bool

		// When response is truncated (the IsTruncated element value in the response
		// is true), you can use the key name in this field as marker in the subsequent
		// request to get next set of objects.
		//
		// NOTE: This element is returned only if you have delimiter request parameter
		// specified.
		ContinuationToken     string
		NextContinuationToken string

		// List of objects info for this request.
		Objects []*ObjectInfo

		// List of prefixes for this request.
		Prefixes []string
	}
)

const (
	rootSeparator = "root://"
	// PathSeparator is a path components separator string.
	PathSeparator = string(os.PathSeparator)
)

func userHeaders(attrs []*object.Attribute) map[string]string {
	result := make(map[string]string, len(attrs))

	for _, attr := range attrs {
		result[attr.Key()] = attr.Value()
	}

	return result
}

func objectInfoFromMeta(bkt *BucketInfo, meta *object.Object, prefix string) *ObjectInfo {
	var (
		isDir         bool
		size          int64
		mimeType      string
		creation      time.Time
		filename      = meta.ID().String()
		name, dirname = nameFromObject(meta)
	)

	if !strings.HasPrefix(dirname, prefix) && prefix != rootSeparator {
		return nil
	}

	if ln := len(prefix); ln > 0 && prefix[ln-1:] != PathSeparator {
		prefix += PathSeparator
	}

	userHeaders := userHeaders(meta.Attributes())
	if val, ok := userHeaders[object.AttributeFileName]; ok {
		filename = val
		delete(userHeaders, object.AttributeFileName)
	}

	if val, ok := userHeaders[object.AttributeTimestamp]; !ok {
		// ignore empty value
	} else if dt, err := strconv.ParseInt(val, 10, 64); err == nil {
		creation = time.Unix(dt, 0)
		delete(userHeaders, object.AttributeTimestamp)
	}

	tail := strings.TrimPrefix(dirname, prefix)
	index := strings.Index(tail, PathSeparator)

	if prefix == rootSeparator {
		size = int64(meta.PayloadSize())
		mimeType = http.DetectContentType(meta.Payload())
	} else if index < 0 {
		filename = name
		size = int64(meta.PayloadSize())
		mimeType = http.DetectContentType(meta.Payload())
	} else {
		isDir = true
		filename = tail[:index] + PathSeparator
		userHeaders = nil
	}

	return &ObjectInfo{
		id:    meta.ID(),
		isDir: isDir,

		Bucket:      bkt.Name,
		Name:        filename,
		Created:     creation,
		ContentType: mimeType,
		Headers:     userHeaders,
		Owner:       meta.OwnerID(),
		Size:        size,
	}
}

func nameFromObject(o *object.Object) (string, string) {
	var name = o.ID().String()

	for _, attr := range o.Attributes() {
		if attr.Key() == object.AttributeFileName {
			name = attr.Value()

			break
		}
	}

	return NameFromString(name)
}

// NameFromString splits name into base file name and directory path.
func NameFromString(name string) (string, string) {
	ind := strings.LastIndex(name, PathSeparator)
	return name[ind+1:], name[:ind+1]
}

// ID returns object ID from ObjectInfo.
func (o *ObjectInfo) ID() *object.ID { return o.id }

// IsDir allows to check if object is a directory.
func (o *ObjectInfo) IsDir() bool { return o.isDir }