package procedure

import (
	"bytes"
	"context"
	"fmt"

	corev1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"

	"github.com/BurntSushi/toml"
	apierrors "k8s.io/apimachinery/pkg/api/errors"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
)

const (
	frpsCmName   = "frps-configmap"
	frpsBindPort = 8080

	// FRP configurations: https://github.com/fatedier/frp#configuration-files
	frpsConfigFileKey = "frps.ini"
)

type frpsCm struct{}

// Deploy deploys resource for the specified tenant.
func (f *frpsCm) Deploy(ctx context.Context, baseCli, _ client.Client, info ProcedureInfo) error {
	cm := getFrpsCM(info)
	_, err := controllerutil.CreateOrUpdate(ctx, baseCli, cm, func() error {
		cm.Data = getFrpsCM(info).Data
		return nil
	})
	return err
}

func getFrpsCM(info ProcedureInfo) *corev1.ConfigMap {
	key := ResourceKey(info, frpsCmName)
	res := &corev1.ConfigMap{
		ObjectMeta: metav1.ObjectMeta{
			Name:      key.Name,
			Namespace: key.Namespace,
		},
		Data: map[string]string{frpsConfigFileKey: frpsConfig(frpsBindPort)},
	}
	return res
}

// Status retrieves the status for the deploying step.
func (f *frpsCm) Status(ctx context.Context, baseCli, infraCli client.Reader, info ProcedureInfo) (bool, error) {
	key := ResourceKey(info, f.Name())
	cm := &corev1.ConfigMap{}
	err := baseCli.Get(ctx, key, cm)
	if err != nil {
		return false, err
	}
	if cm.Data == nil || cm.Data[frpsConfigFileKey] == "" {
		return false, fmt.Errorf("frps config %s not exist", frpsConfigFileKey)
	}
	if config := frpsConfig(frpsBindPort); config == "" {
		return false, fmt.Errorf("frps config %s content empty", frpsConfigFileKey)
	} else if config != cm.Data[frpsConfigFileKey] {
		return false, fmt.Errorf("frps config %s content not match: %s", frpsConfigFileKey, cm.Data[frpsConfigFileKey])
	}
	return true, nil
}

// Name returns the name of the step.
func (f *frpsCm) Name() string {
	return frpsCmName
}

// Cleanup cleans up the frps configmap.
func (f *frpsCm) Cleanup(ctx context.Context, baseCli, infraCli client.Client, info ProcedureInfo) error {
	err := baseCli.Delete(ctx, getFrpsCM(info))
	if err != nil && !apierrors.IsNotFound(err) {
		return err
	}
	return nil
}

// NewFrpsCM returns the procedure for configmap.
func NewFrpsCM() *frpsCm {
	return &frpsCm{}
}

// frpsConfig returns the configmap content for frps.
func frpsConfig(bindPort int) string {
	type commonConfig struct {
		BindPort int `toml:"bind_port"`
	}
	type frpsConfig struct {
		Common commonConfig `toml:"common"`
	}
	cfg := &frpsConfig{
		Common: commonConfig{
			BindPort: bindPort,
		},
	}
	buf := new(bytes.Buffer)
	if err := toml.NewEncoder(buf).Encode(cfg); err != nil {
		return ""
	}
	return buf.String()
}

var _ Procedure = &frpsCm{}
