package tlsconfig

import (
	"crypto/tls"
	"errors"
	"fmt"
	"sync"

	tunnellog "github.com/cloudflare/cloudflared/log"
	"github.com/getsentry/raven-go"
	log "github.com/sirupsen/logrus"
	"gopkg.in/urfave/cli.v2"
)

// CertReloader can load and reload a TLS certificate from a particular filepath.
// Hooks into tls.Config's GetCertificate to allow a TLS server to update its certificate without restarting.
type CertReloader struct {
	sync.Mutex
	certificate *tls.Certificate
	certPath    string
	keyPath     string
}

// NewCertReloader makes a CertReloader, memorizing the filepaths in the context/flags.
func NewCertReloader(c *cli.Context, f CLIFlags) (*CertReloader, error) {
	if !c.IsSet(f.Cert) {
		return nil, errors.New("CertReloader: cert not provided")
	}
	if !c.IsSet(f.Key) {
		return nil, errors.New("CertReloader: key not provided")
	}
	cr := new(CertReloader)
	cr.certPath = c.String(f.Cert)
	cr.keyPath = c.String(f.Key)
	cr.LoadCert()
	return cr, nil
}

// Cert returns the TLS certificate most recently read by the CertReloader.
func (cr *CertReloader) Cert(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
	cr.Lock()
	defer cr.Unlock()
	return cr.certificate, nil
}

// LoadCert loads a TLS certificate from the CertReloader's specified filepath.
// Call this after writing a new certificate to the disk (e.g. after renewing a certificate)
func (cr *CertReloader) LoadCert() {
	cr.Lock()
	defer cr.Unlock()

	log.SetFormatter(&tunnellog.JSONFormatter{})
	log.Info("Reloading certificate")
	cert, err := tls.LoadX509KeyPair(cr.certPath, cr.keyPath)

	// Keep the old certificate if there's a problem reading the new one.
	if err != nil {
		raven.CaptureError(fmt.Errorf("Error parsing X509 key pair: %v", err), nil)
		return
	}
	cr.certificate = &cert
}
