package layer

import (
	"encoding/hex"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"

	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
	"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/encryption"
	"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
)

// PathSeparator is a path components separator string.
const 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 *data.BucketInfo, meta *object.Object) *data.ObjectInfo {
	var (
		mimeType string
		creation time.Time
	)

	headers := userHeaders(meta.Attributes())
	delete(headers, object.AttributeFilePath)
	if contentType, ok := headers[object.AttributeContentType]; ok {
		mimeType = contentType
		delete(headers, object.AttributeContentType)
	}
	if val, ok := headers[object.AttributeTimestamp]; !ok { //nolint:revive
		// ignore empty value
	} else if dt, err := strconv.ParseInt(val, 10, 64); err == nil {
		creation = time.Unix(dt, 0)
		delete(headers, object.AttributeTimestamp)
	}

	objID, _ := meta.ID()
	payloadChecksum, _ := meta.PayloadChecksum()
	return &data.ObjectInfo{
		ID:  objID,
		CID: bkt.CID,

		Bucket:        bkt.Name,
		Name:          filepathFromObject(meta),
		Created:       creation,
		ContentType:   mimeType,
		Headers:       headers,
		Owner:         *meta.OwnerID(),
		Size:          meta.PayloadSize(),
		CreationEpoch: meta.CreationEpoch(),
		HashSum:       hex.EncodeToString(payloadChecksum.Value()),
	}
}

func GetObjectSize(objInfo *data.ObjectInfo) (uint64, error) {
	var err error
	fullSize := objInfo.Size

	if objInfo.Headers[AttributeDecryptedSize] != "" {
		if fullSize, err = strconv.ParseUint(objInfo.Headers[AttributeDecryptedSize], 10, 64); err != nil {
			return 0, fmt.Errorf("invalid decrypted size header: %w", err)
		}
	} else if objInfo.Headers[MultipartObjectSize] != "" {
		if fullSize, err = strconv.ParseUint(objInfo.Headers[MultipartObjectSize], 10, 64); err != nil {
			return 0, fmt.Errorf("invalid multipart size header: %w", err)
		}
	}

	return fullSize, nil
}

func FormEncryptionInfo(headers map[string]string) encryption.ObjectEncryption {
	algorithm := headers[AttributeEncryptionAlgorithm]
	return encryption.ObjectEncryption{
		Enabled:   len(algorithm) > 0,
		Algorithm: algorithm,
		HMACKey:   headers[AttributeHMACKey],
		HMACSalt:  headers[AttributeHMACSalt],
	}
}

func addEncryptionHeaders(meta map[string]string, enc encryption.Params) error {
	meta[AttributeEncryptionAlgorithm] = AESEncryptionAlgorithm
	hmacKey, hmacSalt, err := enc.HMAC()
	if err != nil {
		return fmt.Errorf("get hmac: %w", err)
	}
	meta[AttributeHMACKey] = hex.EncodeToString(hmacKey)
	meta[AttributeHMACSalt] = hex.EncodeToString(hmacSalt)

	return nil
}

func filepathFromObject(o *object.Object) string {
	for _, attr := range o.Attributes() {
		if attr.Key() == object.AttributeFilePath {
			return attr.Value()
		}
	}
	objID, _ := o.ID()
	return objID.EncodeToString()
}

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