package spiffe

import (
	"bytes"
	"context"
	"crypto"
	"crypto/x509"
	"encoding/pem"
	"fmt"

	intoto "github.com/in-toto/in-toto-golang/in_toto"
	"github.com/spiffe/go-spiffe/v2/workloadapi"
)

/*
SVIDDetails captures the Private Key, Root and Intermediate Certificate
from the SVID provided by spire for the workload.
*/
type SVIDDetails struct {
	PrivateKey    crypto.Signer
	Certificate   *x509.Certificate
	Intermediates []*x509.Certificate
}

/*
SVIDFetcher uses the context to connect to the spire and get the SVID associated with
the workload.
*/
type SVIDFetcher interface {
	FetchX509Context(ctx context.Context) (*workloadapi.X509Context, error)
	Close() error
}

/*
NewClient takes the context and the provided spire agent socket path in order to initialize
the workload API.
*/
func NewClient(ctx context.Context, socketPath string) (SVIDFetcher, error) {
	return workloadapi.New(ctx, workloadapi.WithAddr(socketPath))
}

/*
GetSVID attempts to request an SVID from the provided SPIRE Workload API socket.
If attestation succeeds and an SVID is acquired the resulting X509 key &
certificate pair will be returned as well as any intermediate certificates
needed to establish trust to trust domain's root.
*/
func GetSVID(ctx context.Context, client SVIDFetcher) (SVIDDetails, error) {
	s := SVIDDetails{}
	svidContext, err := client.FetchX509Context(ctx)
	if err != nil {
		return s, fmt.Errorf("error fetching spiffe x.509 context: %w", err)
	}

	svid := svidContext.DefaultSVID()
	if len(svid.Certificates) <= 0 {
		return s, fmt.Errorf("no certificates in svid")
	}

	if svid.PrivateKey == nil {
		return s, fmt.Errorf("svid has no key")
	}

	s.PrivateKey = svid.PrivateKey
	s.Certificate = svid.Certificates[0]
	s.Intermediates = svid.Certificates[1:]
	return s, nil
}

/*
InTotoKey uses the private key and certificate obtained from Spire to initialize
intoto.key to be used for signing.
*/
func (s SVIDDetails) InTotoKey() (intoto.Key, error) {
	key := intoto.Key{}
	keyBytes, err := x509.MarshalPKCS8PrivateKey(s.PrivateKey)
	if err != nil {
		return key, fmt.Errorf("failed to marshal svid key: %w", err)
	}

	keyPemBytes := pem.EncodeToMemory(&pem.Block{
		Type:  "PRIVATE KEY",
		Bytes: keyBytes,
	})

	err = key.LoadKeyReaderDefaults(bytes.NewReader(keyPemBytes))
	if err != nil {
		return key, fmt.Errorf("failed to load key from spire: %w", err)
	}

	key.KeyVal.Certificate = string(pem.EncodeToMemory(&pem.Block{Bytes: s.Certificate.Raw, Type: "CERTIFICATE"}))
	return key, nil
}
