package clustersconfig

import (
	"flag"
	"fmt"
	"io"
	"log"
	"net"
	"os"
	"path/filepath"
	"strings"
	"text/template"

	yaml "gopkg.in/yaml.v2"
)

var (
	templateDetailsDir = flag.String("template-details-dir",
		filepath.Join(os.TempDir(), "dkl-dir2config"),
		"write details of template execute in this dir")

	templateID = 0
)

type Config struct {
	Hosts        []*Host
	Clusters     []*Cluster
	Configs      []*Template
	StaticPods   map[string][]*Template `yaml:"static_pods"`
	Addons       map[string][]*Template
	SSLConfig    string         `yaml:"ssl_config"`
	CertRequests []*CertRequest `yaml:"cert_requests"`
}

func FromBytes(data []byte) (*Config, error) {
	config := &Config{Addons: make(map[string][]*Template)}
	if err := yaml.Unmarshal(data, config); err != nil {
		return nil, err
	}
	return config, nil
}

func FromFile(path string) (*Config, error) {
	ba, err := os.ReadFile(path)
	if err != nil {
		return nil, err
	}

	return FromBytes(ba)
}

func (c *Config) Host(name string) *Host {
	for _, host := range c.Hosts {
		if host.Name == name {
			return host
		}
	}
	return nil
}

func (c *Config) HostByIP(ip string) *Host {
	for _, host := range c.Hosts {
		if host.IP == ip {
			return host
		}

		for _, otherIP := range host.IPs {
			if otherIP == ip {
				return host
			}
		}
	}
	return nil
}

func (c *Config) HostByMAC(mac string) *Host {
	// a bit of normalization
	mac = strings.Replace(strings.ToLower(mac), "-", ":", -1)

	for _, host := range c.Hosts {
		if strings.ToLower(host.MAC) == mac {
			return host
		}
	}

	return nil
}

func (c *Config) Cluster(name string) *Cluster {
	for _, cluster := range c.Clusters {
		if cluster.Name == name {
			return cluster
		}
	}
	return nil
}

func (c *Config) ConfigTemplate(name string) *Template {
	for _, cfg := range c.Configs {
		if cfg.Name == name {
			return cfg
		}
	}
	return nil
}

func (c *Config) CSR(name string) *CertRequest {
	for _, s := range c.CertRequests {
		if s.Name == name {
			return s
		}
	}
	return nil
}

func (c *Config) SaveTo(path string) error {
	ba, err := yaml.Marshal(c)
	if err != nil {
		return err
	}

	return os.WriteFile(path, ba, 0600)
}

type Template struct {
	Name     string
	Template string
}

func (t *Template) Execute(contextName, elementName string, wr io.Writer, data interface{}, extraFuncs map[string]interface{}) error {
	var templateFuncs = map[string]interface{}{
		"indent": func(indent, s string) (indented string) {
			indented = indent + strings.Replace(s, "\n", "\n"+indent, -1)
			return
		},
		"yaml": func(v any) (s string, err error) {
			ba, err := yaml.Marshal(v)
			s = string(ba)
			return
		},
	}

	for name, f := range extraFuncs {
		templateFuncs[name] = f
	}

	tmpl, err := template.New(t.Name).
		Funcs(templateFuncs).
		Parse(t.Template)
	if err != nil {
		return err
	}

	if *templateDetailsDir != "" {
		templateID++

		base := filepath.Join(*templateDetailsDir, contextName, fmt.Sprintf("%s-%03d", elementName, templateID))
		os.MkdirAll(base, 0700)

		base += string(filepath.Separator)
		log.Print("writing template details: ", base, "{in,data,out}")

		if err := os.WriteFile(base+"in", []byte(t.Template), 0600); err != nil {
			return err
		}

		yamlBytes, err := yaml.Marshal(data)
		if err != nil {
			return err
		}

		if err := os.WriteFile(base+"data", yamlBytes, 0600); err != nil {
			return err
		}

		out, err := os.Create(base + "out")
		if err != nil {
			return err
		}

		defer out.Close()

		wr = io.MultiWriter(wr, out)
	}

	return tmpl.Execute(wr, data)
}

// Host represents a host served by this server.
type Host struct {
	WithRev

	Template bool `json:",omitempty"`

	Name        string
	Labels      map[string]string `json:",omitempty"`
	Annotations map[string]string `json:",omitempty"`

	MAC     string `json:",omitempty"`
	IP      string
	IPs     []string `json:",omitempty"`
	Cluster string
	Group   string

	Net    string
	IPFrom map[string]string `json:",omitempty" yaml:"ip_from"`

	IPXE            string `json:",omitempty"`
	Kernel          string
	Initrd          string
	BootstrapConfig string `yaml:"bootstrap_config"`
	Config          string
	Versions        map[string]string

	StaticPods string `yaml:"static_pods"`

	Vars Vars
}

// Vars store user-defined key-values
type Vars map[string]interface{}

// Cluster represents a cluster of hosts, allowing for cluster-wide variables.
type Cluster struct {
	WithRev

	Name        string
	Labels      map[string]string
	Annotations map[string]string

	Domain  string
	Addons  []string
	Subnets struct {
		Services string
		Pods     string
	}

	Vars Vars
}

func (c *Cluster) KubernetesSvcIP() net.IP {
	return c.NthSvcIP(1)
}

func (c *Cluster) DNSSvcIP() net.IP {
	return c.NthSvcIP(2)
}

func (c *Cluster) NthSvcIP(n byte) net.IP {
	_, cidr, err := net.ParseCIDR(c.Subnets.Services)
	if err != nil {
		panic(fmt.Errorf("invalid services CIDR: %v", err))
	}

	ip := cidr.IP
	ip[len(ip)-1] += n

	return ip
}
