package config

import (
	"fmt"

	"metis/pkg/inventory"
)

// NodeConfig represents boot-time configuration to inject.
type NodeConfig struct {
	Hostname   string            `json:"hostname"`
	IP         string            `json:"ip"`
	K3s        K3sConfig         `json:"k3s"`
	SSHUser    string            `json:"ssh_user,omitempty"`
	SSHKeys    []string          `json:"ssh_keys,omitempty"`
	Labels     map[string]string `json:"labels,omitempty"`
	Taints     []string          `json:"taints,omitempty"`
	Fstab      []FstabEntry      `json:"fstab,omitempty"`
	USBScratch *USBScratchConfig `json:"usb_scratch,omitempty"`
	Secrets    map[string]string `json:"secrets,omitempty"` // optional key/values for local agent use
}

// K3sConfig includes role and token/url.
type K3sConfig struct {
	Role    string            `json:"role"`
	Version string            `json:"version,omitempty"`
	URL     string            `json:"url,omitempty"`
	Token   string            `json:"token,omitempty"`
	Args    []string          `json:"args,omitempty"`
	Labels  map[string]string `json:"labels,omitempty"`
	Taints  []string          `json:"taints,omitempty"`
}

// FstabEntry for Longhorn or other mounts.
type FstabEntry struct {
	Source     string `json:"source,omitempty"`
	UUID       string `json:"uuid,omitempty"`
	Label      string `json:"label,omitempty"`
	Mountpoint string `json:"mountpoint"`
	FS         string `json:"fs"`
	Options    string `json:"options"`
}

// USBScratchConfig describes a recovery USB disk and its bind mounts.
type USBScratchConfig struct {
	Mountpoint  string   `json:"mountpoint"`
	UUID        string   `json:"uuid,omitempty"`
	Label       string   `json:"label,omitempty"`
	FS          string   `json:"fs,omitempty"`
	BindTargets []string `json:"bind_targets,omitempty"`
}

// Build creates a NodeConfig from inventory.
func Build(inv *inventory.Inventory, nodeName string) (*NodeConfig, error) {
	n, cls, err := inv.FindNode(nodeName)
	if err != nil {
		return nil, err
	}
	labels := map[string]string{}
	for k, v := range cls.DefaultLabels {
		labels[k] = v
	}
	for k, v := range n.Labels {
		labels[k] = v
	}
	taints := append([]string{}, cls.DefaultTaints...)
	taints = append(taints, n.Taints...)
	k3sVersion := cls.K3sVersion
	if n.K3sVersion != "" {
		k3sVersion = n.K3sVersion
	}

	cfg := &NodeConfig{
		Hostname: n.Hostname,
		IP:       n.IP,
		SSHUser:  n.SSHUser,
		SSHKeys:  n.SSHAuthorized,
		Labels:   labels,
		Taints:   taints,
		K3s: K3sConfig{
			Role:    n.K3sRole,
			Version: k3sVersion,
			URL:     n.K3sURL,
			Token:   n.K3sToken,
			Labels:  labels,
			Taints:  taints,
		},
	}

	fstab := []FstabEntry{}
	for _, d := range n.LonghornDisks {
		fs := d.FS
		if fs == "" {
			fs = "ext4"
		}
		fstab = append(fstab, FstabEntry{
			UUID:       d.UUID,
			Mountpoint: d.Mountpoint,
			FS:         fs,
			Options:    "defaults,nofail",
		})
	}
	if n.USBScratch != nil {
		scratch := USBScratchConfig{
			Mountpoint:  n.USBScratch.Mountpoint,
			UUID:        n.USBScratch.UUID,
			Label:       n.USBScratch.Label,
			FS:          n.USBScratch.FS,
			BindTargets: append([]string{}, n.USBScratch.BindTargets...),
		}
		if scratch.FS == "" {
			scratch.FS = "ext4"
		}
		cfg.USBScratch = &scratch
		fstab = append(fstab, FstabEntry{
			UUID:       scratch.UUID,
			Label:      scratch.Label,
			Mountpoint: scratch.Mountpoint,
			FS:         scratch.FS,
			Options:    "defaults,nofail",
		})
		for _, target := range scratch.BindTargets {
			fstab = append(fstab, FstabEntry{
				Source:     scratch.Mountpoint,
				Mountpoint: target,
				FS:         "none",
				Options:    "bind,nofail",
			})
		}
	}
	cfg.Fstab = fstab
	if cfg.Hostname == "" || cfg.IP == "" {
		return nil, fmt.Errorf("hostname/ip required for node %s", nodeName)
	}
	return cfg, nil
}
