package artifact

import (
	"compress/gzip"
	"fmt"
	"hash"
	"io"
)

// Source is a binary data source. Sources must be streamed in their entirety before their
// checksums can be validated. Sources are closable. It is up to the producer to determine who
// is responsible for closing the Source.
type Source interface {
	io.ReadCloser
	ChecksumVerifier
}

// ChecksumVerifier verifies the checksum of a data source.
type ChecksumVerifier interface {
	// VerifyChecksum returns true if ExpectedChecksum() is equal to ActualChecksum(). Otherwise
	// it returns false. If expected and actual are nil, it returns true.
	VerifyChecksum() bool

	// ExpectedChecksum returns the expected checksum of the underlying data.
	ExpectedChecksum() []byte

	// ActualChecksum returns the actual checksum of underlying data.
	ActualChecksum() []byte
}

// GzippedWithChecksum creates a gzip reader around rc and a checksum verifier.
// The returned Source should be used to read the artifact contents.
func GzippedWithChecksum(rc io.ReadCloser, digest hash.Hash, expect []byte) (Source, error) {
	gzipReader, err := gzip.NewReader(rc)
	if err != nil {
		return nil, fmt.Errorf("getting gzip reader: %w", err)
	}
	gzipped := &gzipReadCloser{
		Reader: gzipReader,
		body:   rc,
	}
	return WithChecksum(gzipped, digest, expect)
}

// WithChecksum creates a checksumVerifier. The digest is used to calculate srcs checksum.
// The returned Source should be used to read the artifact contents.
func WithChecksum(rc io.ReadCloser, digest hash.Hash, expect []byte) (Source, error) {
	parsedExpectedChecksum, err := ParseGNUChecksum(expect)
	if err != nil {
		return nil, fmt.Errorf("parsing expected checksum: %w", err)
	}
	return struct {
		io.Reader
		io.Closer
		ChecksumVerifier
	}{
		Reader:           io.TeeReader(rc, digest),
		Closer:           rc,
		ChecksumVerifier: checksumVerifier{expect: parsedExpectedChecksum, digest: digest},
	}, nil
}

// WithNopChecksum turns rc into a Source that nops when the ChecksumVerifier methods are called.
// Both ActualChecksum() and ExpectedChecksum() will return nil.
func WithNopChecksum(rc io.ReadCloser) Source {
	return struct {
		io.ReadCloser
		ChecksumVerifier
	}{
		ReadCloser:       rc,
		ChecksumVerifier: nopChecksumVerifier{},
	}
}

type gzipReadCloser struct {
	*gzip.Reader
	body io.ReadCloser
}

func (g *gzipReadCloser) Close() error {
	g.Reader.Close()
	return g.body.Close()
}
