//
// Copyright 2021 The Sigstore Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package static

import (
	"bytes"
	"crypto/x509"
	"encoding/base64"
	"io"

	v1 "github.com/google/go-containerregistry/pkg/v1"
	"github.com/google/go-containerregistry/pkg/v1/types"
	"github.com/sigstore/cosign/v2/pkg/cosign/bundle"
	"github.com/sigstore/cosign/v2/pkg/oci"
	"github.com/sigstore/sigstore/pkg/cryptoutils"
)

const (
	SignatureAnnotationKey        = "dev.cosignproject.cosign/signature"
	CertificateAnnotationKey      = "dev.sigstore.cosign/certificate"
	ChainAnnotationKey            = "dev.sigstore.cosign/chain"
	BundleAnnotationKey           = "dev.sigstore.cosign/bundle"
	RFC3161TimestampAnnotationKey = "dev.sigstore.cosign/rfc3161timestamp"
)

// NewSignature constructs a new oci.Signature from the provided options.
func NewSignature(payload []byte, b64sig string, opts ...Option) (oci.Signature, error) {
	o, err := makeOptions(opts...)
	if err != nil {
		return nil, err
	}
	return &staticLayer{
		b:      payload,
		b64sig: b64sig,
		opts:   o,
	}, nil
}

// NewAttestation constructs a new oci.Signature from the provided options.
// Since Attestation is treated just like a Signature but the actual signature
// is baked into the payload, the Signature does not actually have
// the Base64Signature.
func NewAttestation(payload []byte, opts ...Option) (oci.Signature, error) {
	return NewSignature(payload, "", opts...)
}

// Copy constructs a new oci.Signature from the provided one.
func Copy(sig oci.Signature) (oci.Signature, error) {
	payload, err := sig.Payload()
	if err != nil {
		return nil, err
	}
	b64sig, err := sig.Base64Signature()
	if err != nil {
		return nil, err
	}
	var opts []Option

	mt, err := sig.MediaType()
	if err != nil {
		return nil, err
	}
	opts = append(opts, WithLayerMediaType(mt))

	ann, err := sig.Annotations()
	if err != nil {
		return nil, err
	}
	opts = append(opts, WithAnnotations(ann))

	bundle, err := sig.Bundle()
	if err != nil {
		return nil, err
	}
	opts = append(opts, WithBundle(bundle))

	rfc3161Timestamp, err := sig.RFC3161Timestamp()
	if err != nil {
		return nil, err
	}
	opts = append(opts, WithRFC3161Timestamp(rfc3161Timestamp))

	cert, err := sig.Cert()
	if err != nil {
		return nil, err
	}
	if cert != nil {
		rawCert, err := cryptoutils.MarshalCertificateToPEM(cert)
		if err != nil {
			return nil, err
		}
		chain, err := sig.Chain()
		if err != nil {
			return nil, err
		}
		rawChain, err := cryptoutils.MarshalCertificatesToPEM(chain)
		if err != nil {
			return nil, err
		}
		opts = append(opts, WithCertChain(rawCert, rawChain))
	}
	return NewSignature(payload, b64sig, opts...)
}

type staticLayer struct {
	b      []byte
	b64sig string
	opts   *options
}

var _ v1.Layer = (*staticLayer)(nil)
var _ oci.Signature = (*staticLayer)(nil)

// Annotations implements oci.Signature
func (l *staticLayer) Annotations() (map[string]string, error) {
	m := make(map[string]string, len(l.opts.Annotations)+1)
	for k, v := range l.opts.Annotations {
		m[k] = v
	}
	m[SignatureAnnotationKey] = l.b64sig
	return m, nil
}

// Payload implements oci.Signature
func (l *staticLayer) Payload() ([]byte, error) {
	return l.b, nil
}

// Signature implements oci.Signature
func (l *staticLayer) Signature() ([]byte, error) {
	b64sig, err := l.Base64Signature()
	if err != nil {
		return nil, err
	}
	return base64.StdEncoding.DecodeString(b64sig)
}

// Base64Signature implements oci.Signature
func (l *staticLayer) Base64Signature() (string, error) {
	return l.b64sig, nil
}

// Cert implements oci.Signature
func (l *staticLayer) Cert() (*x509.Certificate, error) {
	certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(l.opts.Cert))
	if err != nil {
		return nil, err
	}
	if len(certs) == 0 {
		return nil, nil
	}
	return certs[0], nil
}

// Chain implements oci.Signature
func (l *staticLayer) Chain() ([]*x509.Certificate, error) {
	certs, err := cryptoutils.LoadCertificatesFromPEM(bytes.NewReader(l.opts.Chain))
	if err != nil {
		return nil, err
	}
	return certs, nil
}

// Bundle implements oci.Signature
func (l *staticLayer) Bundle() (*bundle.RekorBundle, error) {
	return l.opts.Bundle, nil
}

// RFC3161Timestamp implements oci.Signature
func (l *staticLayer) RFC3161Timestamp() (*bundle.RFC3161Timestamp, error) {
	return l.opts.RFC3161Timestamp, nil
}

// Digest implements v1.Layer
func (l *staticLayer) Digest() (v1.Hash, error) {
	h, _, err := v1.SHA256(bytes.NewReader(l.b))
	return h, err
}

// DiffID implements v1.Layer
func (l *staticLayer) DiffID() (v1.Hash, error) {
	h, _, err := v1.SHA256(bytes.NewReader(l.b))
	return h, err
}

// Compressed implements v1.Layer
func (l *staticLayer) Compressed() (io.ReadCloser, error) {
	return io.NopCloser(bytes.NewReader(l.b)), nil
}

// Uncompressed implements v1.Layer
func (l *staticLayer) Uncompressed() (io.ReadCloser, error) {
	return io.NopCloser(bytes.NewReader(l.b)), nil
}

// Size implements v1.Layer
func (l *staticLayer) Size() (int64, error) {
	return int64(len(l.b)), nil
}

// MediaType implements v1.Layer
func (l *staticLayer) MediaType() (types.MediaType, error) {
	return l.opts.LayerMediaType, nil
}
