package main

import (
	"fmt"
	"io"
	"time"

	"github.com/cloudflare/cfssl/helpers"
	"github.com/cloudflare/cfssl/log"
	restful "github.com/emicklei/go-restful"
)

var clusterCAs = newClusterSecretKV[CA]("CAs")

func wsClusterCAs(req *restful.Request, resp *restful.Response) {
	clusterName := req.PathParameter("cluster-name")
	clusterCAs.WsList(resp, clusterName+"/")
}

func getUsableClusterCA(cluster, name string) (ca CA, err error) {
	defer func() {
		if err != nil {
			err = fmt.Errorf("cluster %s CA %s: %w", cluster, name, err)
		}
	}()

	key := cluster + "/" + name

	ca, found, err := clusterCAs.Get(key)
	if err != nil {
		return
	}

	if !found {
		log.Info("new CA in cluster ", cluster, ": ", name)

		err = ca.Init()
		if err != nil {
			return
		}

		err = clusterCAs.Put(key, ca)
		if err != nil {
			return
		}

		return
	}

	checkErr := checkCertUsable(ca.Cert)
	if checkErr != nil {
		log.Infof("cluster %s: CA %s: regenerating certificate: %v", cluster, name, checkErr)

		ca, err = opMutex("renew-ca:"+key, func() (CA, error) {
			ca, found, err := clusterCAs.Get(key)
			if err != nil {
				return ca, err
			}
			if !found {
				return ca, fmt.Errorf("CA not found")
			}

			prevCerts, _ := helpers.ParseCertificatesPEM(ca.Cert)

			err = ca.RenewCert()
			if err != nil {
				return ca, fmt.Errorf("renew: %w", err)
			}

			now := time.Now()
			for _, cert := range prevCerts {
				if cert.NotAfter.Before(now) {
					continue
				}
				certPEM := helpers.EncodeCertificatePEM(cert)
				ca.Cert = append(ca.Cert, certPEM...)
			}

			err = clusterCAs.Put(key, ca)
			return ca, err
		})
		if err != nil {
			return ca, err
		}
	}

	return
}

func getCurrentClusterCA(cluster, name string) (ca CA, err error) {
	ca, err = getUsableClusterCA(cluster, name)
	if err != nil {
		return
	}

	certs, err := helpers.ParseCertificatesPEM(ca.Cert)
	if err != nil {
		return
	}

	ca.Cert = helpers.EncodeCertificatePEM(certs[0])
	return
}

func caExtraCerts(cluster, name string) (extra string, err error) {
	cfg, err := readConfig()
	if err != nil {
		return
	}
	if cfg.ExtraCaCerts != nil {
		extra = cfg.ExtraCaCerts[cluster+"/"+name]
	}
	return
}

var clusterCASignedKeys = newClusterSecretKV[KeyCert]("CA-signed-keys")

func wsClusterCASignedKeys(req *restful.Request, resp *restful.Response) {
	clusterName := req.PathParameter("cluster-name")
	caName := req.PathParameter("ca-name")
	clusterCASignedKeys.WsList(resp, clusterName+"/"+caName+"/")
}

func wsClusterCASignedKey(req *restful.Request, resp *restful.Response) {
	clusterName := req.PathParameter("cluster-name")
	caName := req.PathParameter("ca-name")
	name := req.PathParameter("signed-name")

	clusterCASignedKeys.WsGet(resp, clusterName+"/"+caName+"/"+name)
}

func wsClusterCACertPut(req *restful.Request, resp *restful.Response) {
	clusterName := req.PathParameter("cluster-name")
	caName := req.PathParameter("ca-name")
	key := clusterName + "/" + caName

	body, err := io.ReadAll(req.Request.Body)
	if err != nil {
		wsError(resp, fmt.Errorf("read body: %w", err))
		return
	}

	certs, err := helpers.ParseCertificatesPEM(body)
	if err != nil {
		wsError(resp, fmt.Errorf("invalid certificate(s): %w", err))
		return
	}

	ca, found, err := clusterCAs.Get(key)
	if err != nil {
		wsError(resp, err)
		return
	}
	if !found {
		wsNotFound(resp)
		return
	}

	caCert, err := ca.ParseCert()
	if err != nil {
		wsError(resp, fmt.Errorf("parse CA cert: %w", err))
		return
	}

	for _, c := range certs {
		if err := c.CheckSignatureFrom(caCert); err != nil {
			wsError(resp, fmt.Errorf("certificate not signed by this CA: %w", err))
			return
		}
	}

	ca.Cert = body

	if err := clusterCAs.Put(key, ca); err != nil {
		wsError(resp, err)
		return
	}
}
