package main

import (
	"log"

	"m.cluseau.fr/go/watchable"
	"novit.tech/direktil/pkg/localconfig"
)

type PublicState struct {
	ServerVersion string
	UIHash        string
	Store         struct {
		New  bool
		Open bool
	}
}

var wPublicState = watchable.New[PublicState]()

type State struct {
	HasConfig bool

	Store struct {
		DownloadToken string
		KeyNames      []string
		Salt          []byte
	}

	Clusters []ClusterState
	Hosts    []HostState
	Config   *localconfig.Config

	Downloads map[string]DownloadSpec

	HostTemplates []string
}

type ClusterState struct {
	Name      string
	Addons    bool
	Passwords []string
	Tokens    []string
	CAs       []CAState
}

type HostState struct {
	Name    string
	Cluster string
	IPs     []string

	Template string `json:",omitempty"`
}

type CAState struct {
	Name   string
	Signed []string
}

var wState = watchable.New[State]()

func init() {
	wState.Set(State{Downloads: map[string]DownloadSpec{}})
}

func updateState() {
	log.Print("updating state")

	// store key names
	keyNames := make([]string, 0, len(secStore.Keys))
	for _, key := range secStore.Keys {
		keyNames = append(keyNames, key.Name)
	}

	// config
	cfg, err := readConfig()
	if err != nil {
		wState.Change(func(v *State) { v.HasConfig = false; v.Config = nil; v.Store.KeyNames = keyNames })
		return
	}

	if secStore.IsNew() || !secStore.Unlocked() {
		wState.Change(func(v *State) { v.HasConfig = false; v.Config = nil; v.Store.KeyNames = keyNames })
		return
	}

	// remove heavy data
	clusters := make([]ClusterState, 0, len(cfg.Clusters))
	for _, cluster := range cfg.Clusters {
		c := ClusterState{
			Name:   cluster.Name,
			Addons: len(cluster.Addons) != 0,
		}

		c.Passwords, err = clusterPasswords.Keys(c.Name + "/")
		if err != nil {
			log.Print("failed to read cluster passwords: ", err)
		}
		c.Tokens, err = clusterTokens.Keys(c.Name + "/")
		if err != nil {
			log.Print("failed to read cluster tokens: ", err)
		}

		caNames, err := clusterCAs.Keys(c.Name + "/")
		if err != nil {
			log.Print("failed to read cluster CAs: ", err)
		}
		for _, caName := range caNames {
			ca := CAState{Name: caName}

			signedNames, err := clusterCASignedKeys.Keys(c.Name + "/" + caName + "/")
			if err != nil {
				log.Print("failed to read cluster CA signed keys: ", err)
			}
			for _, signedName := range signedNames {
				ca.Signed = append(ca.Signed, signedName)
			}

			c.CAs = append(c.CAs, ca)
		}

		clusters = append(clusters, c)
	}

	hfts, err := hostsFromTemplate.List("")
	if err != nil {
		log.Print("failed to read hosts from template: ", err)
	}

	hosts := make([]HostState, 0, len(cfg.Hosts)+len(hfts))
	for _, host := range cfg.Hosts {
		h := HostState{
			Name:    host.Name,
			Cluster: host.ClusterName,
			IPs:     host.IPs,
		}
		hosts = append(hosts, h)
	}

	for _, kv := range hfts {
		name, hft := kv.K, kv.V
		h := HostState{
			Name:    name,
			Cluster: hft.ClusterName(cfg),
			IPs:     []string{hft.IP},

			Template: hft.Template,
		}
		hosts = append(hosts, h)
	}

	hostTemplates := make([]string, len(cfg.HostTemplates))
	for i, ht := range cfg.HostTemplates {
		hostTemplates[i] = ht.Name
	}

	// done
	wState.Change(func(v *State) {
		v.HasConfig = true
		v.Store.KeyNames = keyNames
		v.Store.Salt = secStore.Salt[:]
		v.Clusters = clusters
		v.Hosts = hosts
		v.HostTemplates = hostTemplates
	})
}
