package data

import (
	"encoding/xml"
	"strings"
	"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"
	"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)

const (
	bktSettingsObject                  = ".s3-settings"
	bktCORSConfigurationObject         = ".s3-cors"
	bktNotificationConfigurationObject = ".s3-notifications"

	VersioningUnversioned = "Unversioned"
	VersioningEnabled     = "Enabled"
	VersioningSuspended   = "Suspended"
)

type (
	// BucketInfo stores basic bucket data.
	BucketInfo struct {
		Name                    string // container name from system attribute
		Zone                    string // container zone from system attribute
		CID                     cid.ID
		Owner                   user.ID
		Created                 time.Time
		LocationConstraint      string
		ObjectLockEnabled       bool
		HomomorphicHashDisabled bool
		APEEnabled              bool
	}

	// ObjectInfo holds S3 object data.
	ObjectInfo struct {
		ID  oid.ID
		CID cid.ID

		Bucket        string
		Name          string
		Size          uint64
		ContentType   string
		Created       time.Time
		CreationEpoch uint64
		HashSum       string
		MD5Sum        string
		Owner         user.ID
		Headers       map[string]string
	}

	// NotificationInfo store info to send s3 notification.
	NotificationInfo struct {
		Name    string
		Version string
		Size    uint64
		HashSum string
	}

	// BucketSettings stores settings such as versioning.
	BucketSettings struct {
		Versioning        string
		LockConfiguration *ObjectLockConfiguration
		CannedACL         string
		OwnerKey          *keys.PublicKey
	}

	// CORSConfiguration stores CORS configuration of a request.
	CORSConfiguration struct {
		XMLName   xml.Name   `xml:"http://s3.amazonaws.com/doc/2006-03-01/ CORSConfiguration" json:"-"`
		CORSRules []CORSRule `xml:"CORSRule" json:"CORSRules"`
	}

	// CORSRule stores rules for CORS in a bucket.
	CORSRule struct {
		ID             string   `xml:"ID,omitempty" json:"ID,omitempty"`
		AllowedHeaders []string `xml:"AllowedHeader" json:"AllowedHeaders"`
		AllowedMethods []string `xml:"AllowedMethod" json:"AllowedMethods"`
		AllowedOrigins []string `xml:"AllowedOrigin" json:"AllowedOrigins"`
		ExposeHeaders  []string `xml:"ExposeHeader" json:"ExposeHeaders"`
		MaxAgeSeconds  int      `xml:"MaxAgeSeconds,omitempty" json:"MaxAgeSeconds,omitempty"`
	}
)

// NotificationInfoFromObject creates new NotificationInfo from ObjectInfo.
func NotificationInfoFromObject(objInfo *ObjectInfo, md5Enabled bool) *NotificationInfo {
	return &NotificationInfo{
		Name:    objInfo.Name,
		Version: objInfo.VersionID(),
		Size:    objInfo.Size,
		HashSum: Quote(objInfo.ETag(md5Enabled)),
	}
}

// SettingsObjectName is a system name for a bucket settings file.
func (b *BucketInfo) SettingsObjectName() string { return bktSettingsObject }

// CORSObjectName returns a system name for a bucket CORS configuration file.
func (b *BucketInfo) CORSObjectName() string { return bktCORSConfigurationObject }

func (b *BucketInfo) NotificationConfigurationObjectName() string {
	return bktNotificationConfigurationObject
}

// VersionID returns object version from ObjectInfo.
func (o *ObjectInfo) VersionID() string { return o.ID.EncodeToString() }

// NiceName returns object name for cache.
func (o *ObjectInfo) NiceName() string { return o.Bucket + "/" + o.Name }

// Address returns object address.
func (o *ObjectInfo) Address() oid.Address {
	var addr oid.Address
	addr.SetContainer(o.CID)
	addr.SetObject(o.ID)

	return addr
}

func (o *ObjectInfo) ETag(md5Enabled bool) string {
	if md5Enabled && len(o.MD5Sum) > 0 {
		return o.MD5Sum
	}
	return o.HashSum
}

func (b BucketSettings) Unversioned() bool {
	return b.Versioning == VersioningUnversioned
}

func (b BucketSettings) VersioningEnabled() bool {
	return b.Versioning == VersioningEnabled
}

func (b BucketSettings) VersioningSuspended() bool {
	return b.Versioning == VersioningSuspended
}

func Quote(val string) string {
	return "\"" + val + "\""
}

func UnQuote(val string) string {
	return strings.Trim(val, "\"")
}