package pki

import (
	"crypto/ecdsa"
	"crypto/rand"
	"crypto/x509"
	"crypto/x509/pkix"
	"encoding/pem"
	"errors"
	"io/ioutil"
	"math/big"
	"os"
	"path"
	"time"
)

var passwordCA string

func SetCAPassword(pass string) {
	passwordCA = pass
}

func CACertPath() string {
	return path.Join(PKIDir, "shared", "ca.pem")
}

func CAPrivkeyPath() string {
	return path.Join(PKIDir, "ca.key")
}

func GenerateCA(notBefore time.Time, notAfter time.Time) error {
	ca := &x509.Certificate{
		SerialNumber: big.NewInt(0),
		Subject: pkix.Name{
			Organization:       []string{"EPITA"},
			OrganizationalUnit: []string{"SRS laboratory"},
			Country:            []string{"FR"},
			Locality:           []string{"Paris"},
			CommonName:         "FIC CA",
		},
		NotBefore:             notBefore,
		NotAfter:              notAfter,
		IsCA:                  true,
		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth},
		KeyUsage:              x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
		BasicConstraintsValid: true,
	}

	// Ensure directories exists
	os.Mkdir(PKIDir, 0751)
	os.Mkdir(path.Join(PKIDir, "shared"), 0751)

	pub, priv, err := GeneratePrivKey()
	if err != nil {
		return err
	}

	ca_b, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv)
	if err != nil {
		return err
	}

	// Save certificate to file
	if err := saveCertificate(CACertPath(), ca_b); err != nil {
		return err
	}

	// Save private key to file
	if err := savePrivateKeyEncrypted(CAPrivkeyPath(), priv, passwordCA); err != nil {
		return err
	}

	return nil
}

func LoadCA() (priv ecdsa.PrivateKey, ca x509.Certificate, err error) {
	// Load certificate
	if fd, errr := os.Open(CACertPath()); errr != nil {
		return priv, ca, errr
	} else {
		defer fd.Close()
		if cert, errr := ioutil.ReadAll(fd); errr != nil {
			return priv, ca, errr
		} else {
			block, _ := pem.Decode(cert)
			if block == nil || block.Type != "CERTIFICATE" {
				return priv, ca, errors.New("failed to decode PEM block containing certificate")
			}
			if catmp, errr := x509.ParseCertificate(block.Bytes); errr != nil {
				return priv, ca, errr
			} else if catmp == nil {
				return priv, ca, errors.New("failed to parse certificate")
			} else {
				ca = *catmp
			}
		}
	}

	// Load private key
	if fd, errr := os.Open(CAPrivkeyPath()); errr != nil {
		return priv, ca, errr
	} else {
		defer fd.Close()
		if privkey, errr := ioutil.ReadAll(fd); errr != nil {
			return priv, ca, errr
		} else {
			block, _ := pem.Decode(privkey)
			if block == nil || block.Type != "EC PRIVATE KEY" {
				return priv, ca, errors.New("failed to decode PEM block containing EC private key")
			}

			var decrypted_der []byte
			if x509.IsEncryptedPEMBlock(block) {
				decrypted_der, err = x509.DecryptPEMBlock(block, []byte(passwordCA))
				if err != nil {
					return
				}
			} else {
				decrypted_der = block.Bytes
			}

			if tmppriv, errr := x509.ParseECPrivateKey(decrypted_der); errr != nil {
				return priv, ca, errr
			} else if tmppriv == nil {
				return priv, ca, errors.New("failed to parse private key")
			} else {
				priv = *tmppriv
			}
		}
	}

	return
}
