package erasurecode

import (
	"fmt"

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

// Verify verifies that parts are well formed.
// All parts are expected to be non-nil.
// The number of parts must be equal to `total` field of the EC header
// and parts must be sorted by index.
func (c *Constructor) Verify(parts []*objectSDK.Object) error {
	c.clear()

	var headerLength int
	for i := range parts {
		if parts[i] == nil {
			return ErrMalformedSlice
		}

		var err error
		headerLength, err = validatePart(parts, i, headerLength)
		if err != nil {
			return err
		}
	}

	p0 := parts[0]
	for i := 1; i < len(parts); i++ {
		// This part must be kept in sync with copyRequiredFields().
		pi := parts[i]
		if p0.OwnerID().Equals(pi.OwnerID()) {
			return fmt.Errorf("%w: owner id mismatch: %s != %s", ErrMalformedSlice, p0.OwnerID(), pi.OwnerID())
		}
		if p0.Version() == nil && pi.Version() != nil || !p0.Version().Equal(*pi.Version()) {
			return fmt.Errorf("%w: version mismatch: %s != %s", ErrMalformedSlice, p0.Version(), pi.Version())
		}

		cnr0, _ := p0.ContainerID()
		cnri, _ := pi.ContainerID()
		if !cnr0.Equals(cnri) {
			return fmt.Errorf("%w: container id mismatch: %s != %s", ErrMalformedSlice, cnr0, cnri)
		}
	}

	if err := c.fillHeader(parts); err != nil {
		return err
	}
	c.fillPayload(parts)

	ok, err := c.enc.Verify(c.headerShards)
	if err != nil {
		return err
	}
	if !ok {
		return ErrMalformedSlice
	}

	ok, err = c.enc.Verify(c.payloadShards)
	if err != nil {
		return err
	}
	if !ok {
		return ErrMalformedSlice
	}
	return nil
}

// copyRequiredFields sets all fields in dst which are copied from src and shared among all chunks.
// src can be either another chunk of full object.
// dst must be a chunk.
func copyRequiredFields(dst *objectSDK.Object, src *objectSDK.Object) {
	dst.SetVersion(src.Version())
	dst.SetOwnerID(src.OwnerID())
	dst.SetCreationEpoch(src.CreationEpoch())
	dst.SetSessionToken(src.SessionToken())

	cnr, _ := src.ContainerID()
	dst.SetContainerID(cnr)
}

// validatePart makes i-th part is consistent with the rest.
// If headerLength is not zero it is asserted to be equal in the ec header.
// Otherwise, new headerLength is returned.
func validatePart(parts []*objectSDK.Object, i int, headerLength int) (int, error) {
	ec := parts[i].GetECHeader()
	if ec == nil {
		return headerLength, fmt.Errorf("%w: missing EC header", ErrMalformedSlice)
	}
	if ec.Index() != uint32(i) {
		return headerLength, fmt.Errorf("%w: index=%d, ec.index=%d", ErrMalformedSlice, i, ec.Index())
	}
	if ec.Total() != uint32(len(parts)) {
		return headerLength, fmt.Errorf("%w: len(parts)=%d, total=%d", ErrMalformedSlice, len(parts), ec.Total())
	}
	if headerLength == 0 {
		return int(ec.HeaderLength()), nil
	}
	if ec.HeaderLength() != uint32(headerLength) {
		return headerLength, fmt.Errorf("%w: header length mismatch %d != %d", ErrMalformedSlice, headerLength, ec.HeaderLength())
	}
	return headerLength, nil
}