package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path"
)

const (
	opensslConfig    = "/usr/share/tests/osbuild-composer/x509/openssl.cnf"
	osbuildCAExt     = "osbuild_ca_ext"
	osbuildClientExt = "osbuild_client_ext"
)

type certificateKeyPair struct {
	baseDir string
}

func (ckp certificateKeyPair) remove() {
	err := os.RemoveAll(ckp.baseDir)
	if err != nil {
		log.Printf("cannot delete the certificate key pair: %v", err)
	}
}

func (ckp certificateKeyPair) certificate() string {
	return path.Join(ckp.baseDir, "crt")
}

func (ckp certificateKeyPair) key() string {
	return path.Join(ckp.baseDir, "key")
}

func newSelfSignedCertificateKeyPair(subj string) (*certificateKeyPair, error) {
	dir, err := ioutil.TempDir("", "osbuild-auth-tests-")
	if err != nil {
		return nil, fmt.Errorf("cannot create a temporary directory for the certificate: %v", err)
	}

	ckp := certificateKeyPair{baseDir: dir}

	cmd := exec.Command(
		"openssl", "req", "-nodes", "-x509",
		"-subj", subj,
		"-out", ckp.certificate(),
		"-keyout", ckp.key(),
	)
	err = cmd.Run()
	if err != nil {
		return nil, fmt.Errorf("cannot generate a self-signed certificate: %v", err)
	}

	return &ckp, nil
}

type ca struct {
	BaseDir string
}

func (c ca) remove() {
	err := os.RemoveAll(c.BaseDir)
	if err != nil {
		log.Printf("cannot delete the ca: %v", err)
	}
}

func (c ca) certificate() string {
	return path.Join(c.BaseDir, "ca.cert.pem")
}

func (c ca) key() string {
	return path.Join(c.BaseDir, "private", "ca.key.pem")
}

func newCA(subj string) (*ca, error) {
	baseDir, err := ioutil.TempDir("", "osbuild-auth-tests-ca")
	if err != nil {
		return nil, fmt.Errorf("cannot create a temporary dir for a new CA: %v", err)
	}

	err = os.Mkdir(path.Join(baseDir, "certs"), 0700)
	if err != nil {
		innerErr := os.RemoveAll(baseDir)
		if innerErr != nil {
			log.Print(innerErr)
		}
		return nil, fmt.Errorf("cannot create certs dir for the new CA: %v", err)
	}

	err = os.Mkdir(path.Join(baseDir, "private"), 0700)
	if err != nil {
		innerErr := os.RemoveAll(baseDir)
		if innerErr != nil {
			log.Print(innerErr)
		}
		return nil, fmt.Errorf("cannot create private dir for the new CA: %v", err)
	}

	f, err := os.Create(path.Join(baseDir, "index.txt"))
	if err != nil {
		innerErr := os.RemoveAll(baseDir)
		if innerErr != nil {
			log.Print(innerErr)
		}
		return nil, fmt.Errorf("cannot create index file for the new CA: %v", err)
	}
	f.Close()

	c := ca{
		BaseDir: baseDir,
	}

	cmd := exec.Command(
		"openssl", "req",
		"-config", opensslConfig,
		"-new", "-nodes", "-x509", "-extensions", osbuildCAExt,
		"-subj", subj,
		"-keyout", c.key(),
		"-out", c.certificate(),
	)

	err = cmd.Run()
	if err != nil {
		innerErr := os.RemoveAll(baseDir)
		if innerErr != nil {
			log.Print(innerErr)
		}
		return nil, fmt.Errorf("cannot create the CA: %v", err)
	}

	return &c, nil
}

func (c ca) newCertificateKeyPair(subj, extensions, addext string) (*certificateKeyPair, error) {
	dir, err := ioutil.TempDir("", "osbuild-auth-tests-")
	if err != nil {
		return nil, fmt.Errorf("cannot create a temporary directory for the certificate: %v", err)
	}

	ckp := certificateKeyPair{baseDir: dir}
	certificateRequest := path.Join(dir, "csr")

	args := []string{
		"req", "-new", "-nodes",
		"-subj", subj,
		"-keyout", ckp.key(),
		"-out", certificateRequest,
		"-config", opensslConfig,
	}

	if addext != "" {
		args = append(args, "-addext", addext)
	}

	cmd := exec.Command(
		"openssl",
		args...,
	)

	err = cmd.Run()
	if err != nil {
		return nil, fmt.Errorf("cannot generate a private key and a certificate request: %v", err)
	}

	defer os.Remove(certificateRequest)

	cmd = exec.Command(
		"openssl", "ca",
		"-batch",
		"-config", opensslConfig,
		"-extensions", extensions,
		"-in", certificateRequest,
		"-out", ckp.certificate(),
	)
	// this command must be run in the CA base directory
	cmd.Dir = c.BaseDir

	err = cmd.Run()
	if err != nil {
		return nil, fmt.Errorf("cannot sign the certificate: %v", err)
	}

	return &ckp, nil
}
