package container

import (
	"crypto/sha256"

	"github.com/google/uuid"
	"github.com/nspcc-dev/neofs-api-go/v2/container"
	"github.com/nspcc-dev/neofs-sdk-go/acl"
	cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
	"github.com/nspcc-dev/neofs-sdk-go/netmap"
	"github.com/nspcc-dev/neofs-sdk-go/owner"
	"github.com/nspcc-dev/neofs-sdk-go/session"
	"github.com/nspcc-dev/neofs-sdk-go/signature"
	"github.com/nspcc-dev/neofs-sdk-go/version"
)

type Container struct {
	v2 container.Container

	token *session.Token

	sig *signature.Signature
}

// New creates, initializes and returns blank Container instance.
//
// Defaults:
//  - token: nil;
//  - sig: nil;
//  - basicACL: acl.PrivateBasicRule;
//  - version: version.Current;
//  - nonce: random UUID;
//  - attr: nil;
//  - policy: nil;
//  - ownerID: nil.
func New(opts ...Option) *Container {
	cnrOptions := defaultContainerOptions()

	for i := range opts {
		opts[i](&cnrOptions)
	}

	cnr := new(Container)
	cnr.SetNonceUUID(cnrOptions.nonce)
	cnr.SetBasicACL(cnrOptions.acl)

	if cnrOptions.owner != nil {
		cnr.SetOwnerID(cnrOptions.owner)
	}

	if cnrOptions.policy != nil {
		cnr.SetPlacementPolicy(cnrOptions.policy)
	}

	cnr.SetAttributes(cnrOptions.attributes)
	cnr.SetVersion(version.Current())

	return cnr
}

// ToV2 returns the v2 Container message.
//
// Nil Container converts to nil.
func (c *Container) ToV2() *container.Container {
	if c == nil {
		return nil
	}

	return &c.v2
}

// NewVerifiedFromV2 constructs Container from NeoFS API V2 Container message.
//
// Does not perform if message meets NeoFS API V2 specification. To do this
// use NewVerifiedFromV2 constructor.
func NewContainerFromV2(c *container.Container) *Container {
	cnr := new(Container)

	if c != nil {
		cnr.v2 = *c
	}

	return cnr
}

// CalculateID calculates container identifier
// based on its structure.
func CalculateID(c *Container) *cid.ID {
	data, err := c.ToV2().StableMarshal(nil)
	if err != nil {
		panic(err)
	}

	id := cid.New()
	id.SetSHA256(sha256.Sum256(data))

	return id
}

func (c *Container) Version() *version.Version {
	return version.NewFromV2(c.v2.GetVersion())
}

func (c *Container) SetVersion(v *version.Version) {
	c.v2.SetVersion(v.ToV2())
}

func (c *Container) OwnerID() *owner.ID {
	return owner.NewIDFromV2(c.v2.GetOwnerID())
}

func (c *Container) SetOwnerID(v *owner.ID) {
	c.v2.SetOwnerID(v.ToV2())
}

// Returns container nonce in UUID format.
//
// Returns error if container nonce is not a valid UUID.
func (c *Container) NonceUUID() (uuid.UUID, error) {
	return uuid.FromBytes(c.v2.GetNonce())
}

// SetNonceUUID sets container nonce as UUID.
func (c *Container) SetNonceUUID(v uuid.UUID) {
	data, _ := v.MarshalBinary()
	c.v2.SetNonce(data)
}

func (c *Container) BasicACL() uint32 {
	return c.v2.GetBasicACL()
}

func (c *Container) SetBasicACL(v acl.BasicACL) {
	c.v2.SetBasicACL(uint32(v))
}

func (c *Container) Attributes() Attributes {
	return NewAttributesFromV2(c.v2.GetAttributes())
}

func (c *Container) SetAttributes(v Attributes) {
	c.v2.SetAttributes(v.ToV2())
}

func (c *Container) PlacementPolicy() *netmap.PlacementPolicy {
	return netmap.NewPlacementPolicyFromV2(c.v2.GetPlacementPolicy())
}

func (c *Container) SetPlacementPolicy(v *netmap.PlacementPolicy) {
	c.v2.SetPlacementPolicy(v.ToV2())
}

// SessionToken returns token of the session within
// which container was created.
func (c Container) SessionToken() *session.Token {
	return c.token
}

// SetSessionToken sets token of the session within
// which container was created.
func (c *Container) SetSessionToken(t *session.Token) {
	c.token = t
}

// Signature returns signature of the marshaled container.
func (c Container) Signature() *signature.Signature {
	return c.sig
}

// SetSignature sets signature of the marshaled container.
func (c *Container) SetSignature(sig *signature.Signature) {
	c.sig = sig
}

// Marshal marshals Container into a protobuf binary form.
func (c *Container) Marshal() ([]byte, error) {
	return c.v2.StableMarshal(nil)
}

// Unmarshal unmarshals protobuf binary representation of Container.
func (c *Container) Unmarshal(data []byte) error {
	return c.v2.Unmarshal(data)
}

// MarshalJSON encodes Container to protobuf JSON format.
func (c *Container) MarshalJSON() ([]byte, error) {
	return c.v2.MarshalJSON()
}

// UnmarshalJSON decodes Container from protobuf JSON format.
func (c *Container) UnmarshalJSON(data []byte) error {
	return c.v2.UnmarshalJSON(data)
}