// Copyright (c) 2022 Alibaba Group Holding Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package cert

import (
	"context"
	"fmt"
	"strconv"
	"time"

	v1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
)

type SecretMgr struct {
	client    kubernetes.Interface
	namespace string
}

func NewSecretMgr(namespace string, client kubernetes.Interface) (*SecretMgr, error) {
	secretMgr := &SecretMgr{
		namespace: namespace,
		client:    client,
	}

	return secretMgr, nil
}

func (s *SecretMgr) Update(domain string, secretName string, privateKey []byte, certificate []byte, notBefore time.Time, notAfter time.Time, isRenew bool) error {
	CertLog.Infof("update secret, domain:%s, secretName:%s, notBefore:%v, notAfter:%v, isRenew:%t", domain, secretName, notBefore, notAfter, isRenew)
	name := secretName
	namespace := s.namespace
	namespaceP, secretP := ParseTLSSecret(secretName)
	if namespaceP != "" {
		namespace = namespaceP
		name = secretP
	}

	secret := s.constructSecret(domain, name, namespace, privateKey, certificate, notBefore, notAfter, isRenew)
	_, err := s.client.CoreV1().Secrets(namespace).Get(context.Background(), name, metav1.GetOptions{})
	if err != nil {
		if errors.IsNotFound(err) {
			// create secret
			_, err2 := s.client.CoreV1().Secrets(namespace).Create(context.Background(), secret, metav1.CreateOptions{})
			return err2
		}
		return err
	}
	// check secret annotations
	if _, ok := secret.Annotations["higress.io/cert-domain"]; !ok {
		return fmt.Errorf("the secret name %s is not automatic https secret name for the domain:%s, please rename it in config", secretName, domain)
	}
	_, err1 := s.client.CoreV1().Secrets(namespace).Update(context.Background(), secret, metav1.UpdateOptions{})
	if err1 != nil {
		return err1
	}

	return nil
}

func (s *SecretMgr) constructSecret(domain string, name string, namespace string, privateKey []byte, certificate []byte, notBefore time.Time, notAfter time.Time, isRenew bool) *v1.Secret {
	annotationMap := make(map[string]string, 0)
	annotationMap["higress.io/cert-domain"] = domain
	annotationMap["higress.io/cert-notAfter"] = notAfter.Format("2006-01-02 15:04:05")
	annotationMap["higress.io/cert-notBefore"] = notBefore.Format("2006-01-02 15:04:05")
	annotationMap["higress.io/cert-renew"] = strconv.FormatBool(isRenew)
	annotationMap["higress.io/cert-source"] = string(IssuerTypeLetsencrypt)
	if isRenew {
		annotationMap["higress.io/cert-renew-time"] = time.Now().Format("2006-01-02 15:04:05")
	}
	// Required fields:
	// - Secret.Data["tls.key"] - TLS private key.
	//   Secret.Data["tls.crt"] - TLS certificate.
	dataMap := make(map[string][]byte, 0)
	dataMap["tls.key"] = privateKey
	dataMap["tls.crt"] = certificate
	secret := &v1.Secret{
		ObjectMeta: metav1.ObjectMeta{
			Name:        name,
			Namespace:   namespace,
			Annotations: annotationMap,
		},
		Type: v1.SecretTypeTLS,
		Data: dataMap,
	}
	return secret
}
