package ceph

import (
	"fmt"
	"strings"
	"text/template"

	"github.com/canonical/microceph/microceph/constants"
	"github.com/canonical/microceph/microceph/logger"
)

type CephConfFile struct {
	FsID     string   `json:"fsid"`
	RunDir   string   `json:"runDir"`
	Monitors []string `json:"monitors"`
	PubNet   string   `json:"pubNet"`
}

func (ccf CephConfFile) Render(confFileName string) error {
	if len(confFileName) == 0 {
		logger.Error("need conf file path to render")
		return fmt.Errorf("need conf file path to render")
	}

	if len(ccf.FsID) == 0 {
		logger.Errorf("mandatory param fsid is missing : %v", ccf)
		return fmt.Errorf("mandatory param fsid is missing")
	}

	if len(ccf.RunDir) == 0 {
		logger.Errorf("mandatory param runDir is missing : %v", ccf)
		return fmt.Errorf("mandatory param runDir is missing")
	}

	if len(ccf.Monitors) == 0 {
		logger.Errorf("mandatory param monitor hosts are missing : %v", ccf)
		return fmt.Errorf("mandatory param monitor hosts are missing")
	}

	if len(ccf.PubNet) == 0 {
		logger.Errorf("mandatory param pubNet is missing : %v", ccf)
		return fmt.Errorf("mandatory param pubNet is missing")
	}

	logger.Debugf("writing %s with %+v", confFileName, ccf)

	// Write ceph.conf contents.
	conf := NewCephConfig(confFileName)
	return conf.WriteConfig(
		map[string]any{
			"fsid":   ccf.FsID,
			"runDir": ccf.RunDir,
			// First monitor bootstrap IP as passed to microcluster.
			"monitors": ccf.Monitors,
			"pubNet":   ccf.PubNet,
			"ipv4":     strings.Contains(ccf.PubNet, "."),
			"ipv6":     strings.Contains(ccf.PubNet, ":"),
		}, constants.PermissionUserRwWorldRAccess)
}

type CephKeyringFile struct {
	Name string `json:"name"`
	Key  string `json:"key"`
}

func (ck CephKeyringFile) Render(confDir, confFile string) error {
	if len(ck.Name) == 0 {
		err := fmt.Errorf("no client name provided to render keyring")
		logger.Error(err.Error())
		return err
	}

	if len(ck.Key) == 0 {
		err := fmt.Errorf("no client key provided to render keyring")
		logger.Error(err.Error())
		return err
	}

	conf := NewCephKeyring(confDir, confFile)
	return conf.WriteConfig(map[string]any{
		"name": ck.Name,
		"key":  ck.Key,
	}, constants.PermissionUserRWGroupROWorldNA)
}

// NewCephConfig creates a new ceph configuration file with given name.
func NewCephConfig(configFile string) *Config {
	return &Config{
		configTemplate: template.Must(template.New("cephConf").Parse(`# # Generated by MicroCeph, DO NOT EDIT.
[global]
{{if .runDir}}run dir = {{.runDir}}{{end}}
fsid = {{.fsid}}
mon host = {{.monitors}}
public_network = {{.pubNet}}
auth allow insecure global id reclaim = false
ms bind ipv4 = {{.ipv4}}
ms bind ipv6 = {{.ipv6}}
# https://tracker.ceph.com/issues/70390
bluestore_elastic_shared_blobs = false

[client]
{{if .isCache}}rbd_cache = {{.isCache}}{{end}}
{{if .cacheSize}}rbd_cache_size = {{.cacheSize}}{{end}}
{{if .isCacheWritethrough}}rbd_cache_writethrough_until_flush = {{.isCacheWritethrough}}{{end}}
{{if .cacheMaxDirty}}rbd_cache_max_dirty = {{.cacheMaxDirty}}{{end}}
{{if .cacheTargetDirty}}rbd_cache_target_dirty = {{.cacheTargetDirty}}{{end}}
`)),
		configFile: configFile,
		configDir:  constants.GetPathConst().ConfPath,
	}
}

// NewCephKeyring creates a new Ceph keyring config
func NewCephKeyring(configDir, configFile string) *Config {
	return &Config{
		configTemplate: template.Must(template.New("cephKeyring").Parse(`# Generated by MicroCeph, DO NOT EDIT.
[{{.name}}]
	key = {{.key}}
`)),
		configDir:  configDir,
		configFile: configFile,
	}
}
