package provision

import (
	"crypto/rand"
	"fmt"
	"os"
	"text/template"
)

// newUUID returns a RFC-4122 v4 UUID string. Self-contained (crypto/rand) so
// the package does not depend on the not-yet-implemented idgen package.
func newUUID() (string, error) {
	var b [16]byte
	if _, err := rand.Read(b[:]); err != nil {
		return "", fmt.Errorf("provision: uuid entropy: %w", err)
	}
	b[6] = (b[6] & 0x0f) | 0x40 // version 4
	b[8] = (b[8] & 0x3f) | 0x80 // variant 10
	return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:16]), nil
}

// TemplateRenderer renders the node cloud-init document from a Go text/template
// file (infra/cloud-init/node.yaml.tmpl). It satisfies CloudInitRenderer.
//
// The rendered output carries the one-time bootstrap token and MUST NOT be
// logged. text/template (not html/template) is used because the output is YAML.
type TemplateRenderer struct {
	tmpl *template.Template
}

// NewTemplateRenderer parses the cloud-init template file at path.
func NewTemplateRenderer(path string) (*TemplateRenderer, error) {
	raw, err := os.ReadFile(path)
	if err != nil {
		return nil, fmt.Errorf("provision: read cloud-init template: %w", err)
	}
	return NewTemplateRendererFromString(string(raw))
}

// NewTemplateRendererFromString parses an in-memory template (used in tests).
func NewTemplateRendererFromString(body string) (*TemplateRenderer, error) {
	t, err := template.New("node-cloud-init").Option("missingkey=error").Parse(body)
	if err != nil {
		return nil, fmt.Errorf("provision: parse cloud-init template: %w", err)
	}
	return &TemplateRenderer{tmpl: t}, nil
}

// Render executes the template against data.
func (r *TemplateRenderer) Render(data CloudInitData) (string, error) {
	var sb stringsBuilder
	if err := r.tmpl.Execute(&sb, data); err != nil {
		return "", fmt.Errorf("provision: render cloud-init: %w", err)
	}
	return sb.String(), nil
}

// stringsBuilder is a tiny alias to avoid importing strings just for Builder
// alongside the strings import elsewhere; kept local for clarity.
type stringsBuilder struct{ buf []byte }

func (s *stringsBuilder) Write(p []byte) (int, error) {
	s.buf = append(s.buf, p...)
	return len(p), nil
}
func (s *stringsBuilder) String() string { return string(s.buf) }
