package installer

import (
	"bytes"
	_ "embed"
	"encoding/json"
	"fmt"
	template "html/template"
	"net"
	"net/netip"
	"strings"

	"cuelang.org/go/cue"
	"cuelang.org/go/cue/build"
	"cuelang.org/go/cue/cuecontext"
	"cuelang.org/go/cue/errors"
	"cuelang.org/go/cue/load"
	cueyaml "cuelang.org/go/encoding/yaml"
	helmv2 "github.com/fluxcd/helm-controller/api/v2"
)

//go:embed app_configs/dodo_app.cue
var dodoAppCue []byte

//go:embed app_configs/app_base.cue
var cueBaseConfig []byte

//go:embed app_configs/app_global_env.cue
var cueEnvAppGlobal []byte

//go:embed app_configs/app_global_infra.cue
var cueInfraAppGlobal []byte

type Access struct {
	Type       string `json:"type"`
	Name       string `json:"name"`
	HTTPS      *AccessHTTPS
	SSH        *AccessSSH
	TCP        *AccessTCP
	UDP        *AccessUDP
	PostgreSQL *AccessPostgreSQL
	MongoDB    *AccessMongoDB
	EnvVar     *AccessEnvVar
}

func (a Access) MarshalJSON() ([]byte, error) {
	var buf bytes.Buffer
	switch a.Type {
	case "https":
		if err := json.NewEncoder(&buf).Encode(struct {
			AccessHTTPS
			Type string `json:"type"`
			Name string `json:"name"`
		}{*a.HTTPS, a.Type, a.Name}); err != nil {
			return nil, err
		}
	case "ssh":
		if err := json.NewEncoder(&buf).Encode(struct {
			AccessSSH
			Type string `json:"type"`
			Name string `json:"name"`
		}{*a.SSH, a.Type, a.Name}); err != nil {
			return nil, err
		}
	case "tcp":
		if err := json.NewEncoder(&buf).Encode(struct {
			AccessTCP
			Type string `json:"type"`
			Name string `json:"name"`
		}{*a.TCP, a.Type, a.Name}); err != nil {
			return nil, err
		}
	case "udp":
		if err := json.NewEncoder(&buf).Encode(struct {
			AccessUDP
			Type string `json:"type"`
			Name string `json:"name"`
		}{*a.UDP, a.Type, a.Name}); err != nil {
			return nil, err
		}
	case "postgresql":
		if err := json.NewEncoder(&buf).Encode(struct {
			AccessPostgreSQL
			Type string `json:"type"`
			Name string `json:"name"`
		}{*a.PostgreSQL, a.Type, a.Name}); err != nil {
			return nil, err
		}
	case "mongodb":
		if err := json.NewEncoder(&buf).Encode(struct {
			AccessMongoDB
			Type string `json:"type"`
			Name string `json:"name"`
		}{*a.MongoDB, a.Type, a.Name}); err != nil {
			return nil, err
		}
	case "env_var":
		if err := json.NewEncoder(&buf).Encode(struct {
			AccessEnvVar
			Type string `json:"type"`
			Name string `json:"name"`
		}{*a.EnvVar, a.Type, a.Name}); err != nil {
			return nil, err
		}
	default:
		panic("MUST NOT REACH!")
	}
	return buf.Bytes(), nil
}

type AccessHTTPS struct {
	Address   string `json:"address"`
	AgentName string `json:"agentName,omitempty"`
}

type AccessSSH struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

type AccessTCP struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

type AccessUDP struct {
	Host string `json:"host"`
	Port int    `json:"port"`
}

type AccessPostgreSQL struct {
	Host     string `json:"host"`
	Port     int    `json:"port"`
	Database string `json:"database"`
	Username string `json:"username"`
	Password string `json:"password"`
}

type AccessMongoDB struct {
	Host     string `json:"host"`
	Port     int    `json:"port"`
	Database string `json:"database"`
	Username string `json:"username"`
	Password string `json:"password"`
}

type AccessEnvVar struct {
	Var string `json:"var"`
}

type EnvVar struct {
	Name  string `json:"name"`
	Value string `json:"value"`
}

type rendered struct {
	Name            string
	Readme          string
	Cluster         string
	Namespaces      []Namespace
	Resources       CueAppData
	HelmCharts      HelmCharts
	ContainerImages map[string]ContainerImage
	Ports           []PortForward
	ClusterProxies  map[string]ClusterProxy
	Data            CueAppData
	URL             string
	Help            []HelpDocument
	Icon            string
	Access          []Access
	EnvVars         []EnvVar
	Raw             []byte
}

type Namespace struct {
	Name       string `json:"name"`
	Kubeconfig string `json:"kubeconfig,omitempty"`
}

type HelpDocument struct {
	Title    string
	Contents string
	Children []HelpDocument
}

type ContainerImage struct {
	Registry   string `json:"registry"`
	Repository string `json:"repository"`
	Name       string `json:"name"`
	Tag        string `json:"tag"`
}

type helmChartRef struct {
	Kind string `json:"kind"`
}

type HelmCharts struct {
	Git map[string]HelmChartGitRepo
}

type HelmChartGitRepo struct {
	Address string `json:"address"`
	Branch  string `json:"branch"`
	Path    string `json:"path"`
}

type EnvAppRendered struct {
	rendered
	Config AppInstanceConfig
}

type InfraAppRendered struct {
	rendered
	Config InfraAppInstanceConfig
}

type ClusterProxy struct {
	From string `json:"from"`
	To   string `json:"to"`
}

type PortForward struct {
	Cluster  string  `json:"clusterName,omitempty"`
	Network  Network `json:"network"`
	Protocol string  `json:"protocol"`
	Port     int     `json:"port"`
	Service  struct {
		Name      string `json:"name"`
		Namespace string `json:"namespace,omitempty"`
		Port      int    `json:"port"`
	} `json:"service"`
}

type AppType int

const (
	AppTypeInfra AppType = iota
	AppTypeEnv
)

type App interface {
	Name() string
	Type() AppType
	Slug() string
	Description() string
	Icon() template.HTML
	Schema() Schema
	Namespace() string
}

type InfraConfig struct {
	Name                 string   `json:"pcloudEnvName,omitempty"` // #TODO(gio): change to name
	PublicIP             []net.IP `json:"publicIP,omitempty"`
	InfraNamespacePrefix string   `json:"namespacePrefix,omitempty"`
	InfraAdminPublicKey  []byte   `json:"infraAdminPublicKey,omitempty"`
}

type InfraNetwork struct {
	Name               string `json:"name,omitempty"`
	IngressClass       string `json:"ingressClass,omitempty"`
	CertificateIssuer  string `json:"certificateIssuer,omitempty"`
	AllocatePortAddr   string `json:"allocatePortAddr,omitempty"`
	ReservePortAddr    string `json:"reservePortAddr,omitempty"`
	DeallocatePortAddr string `json:"deallocatePortAddr,omitempty"`
}

type InfraApp interface {
	App
	Render(release Release, infra InfraConfig, networks []InfraNetwork, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (InfraAppRendered, error)
}

type EnvNetwork struct {
	DNS            net.IP `json:"dns,omitempty"`
	DNSInClusterIP net.IP `json:"dnsInClusterIP,omitempty"`
	Ingress        net.IP `json:"ingress,omitempty"`
	Headscale      net.IP `json:"headscale,omitempty"`
	ServicesFrom   net.IP `json:"servicesFrom,omitempty"`
	ServicesTo     net.IP `json:"servicesTo,omitempty"`
}

func NewEnvNetwork(subnet net.IP) (EnvNetwork, error) {
	addr, err := netip.ParseAddr(subnet.String())
	if err != nil {
		return EnvNetwork{}, err
	}
	if !addr.Is4() {
		return EnvNetwork{}, fmt.Errorf("Expected IPv4, got %s instead", addr)
	}
	dns := addr.Next()
	ingress := dns.Next()
	headscale := ingress.Next()
	b := addr.AsSlice()
	if b[3] != 0 {
		return EnvNetwork{}, fmt.Errorf("Expected last byte to be zero, got %d instead", b[3])
	}
	b[3] = 10
	servicesFrom, ok := netip.AddrFromSlice(b)
	if !ok {
		return EnvNetwork{}, fmt.Errorf("Must not reach")
	}
	b[3] = 254
	servicesTo, ok := netip.AddrFromSlice(b)
	if !ok {
		return EnvNetwork{}, fmt.Errorf("Must not reach")
	}
	b[3] = b[2]
	b[2] = b[1]
	b[0] = 10
	b[1] = 44
	dnsInClusterIP, ok := netip.AddrFromSlice(b)
	if !ok {
		return EnvNetwork{}, fmt.Errorf("Must not reach")
	}
	return EnvNetwork{
		DNS:            net.ParseIP(dns.String()),
		DNSInClusterIP: net.ParseIP(dnsInClusterIP.String()),
		Ingress:        net.ParseIP(ingress.String()),
		Headscale:      net.ParseIP(headscale.String()),
		ServicesFrom:   net.ParseIP(servicesFrom.String()),
		ServicesTo:     net.ParseIP(servicesTo.String()),
	}, nil
}

type EnvConfig struct {
	Id              string     `json:"id,omitempty"`
	InfraName       string     `json:"pcloudEnvName,omitempty"`
	Domain          string     `json:"domain,omitempty"`
	PrivateDomain   string     `json:"privateDomain,omitempty"`
	ContactEmail    string     `json:"contactEmail,omitempty"`
	AdminPublicKey  string     `json:"adminPublicKey,omitempty"`
	PublicIP        []net.IP   `json:"publicIP,omitempty"`
	NameserverIP    []net.IP   `json:"nameserverIP,omitempty"`
	NamespacePrefix string     `json:"namespacePrefix,omitempty"`
	Network         EnvNetwork `json:"network,omitempty"`
}

type EnvApp interface {
	App
	Render(
		release Release,
		env EnvConfig,
		networks []Network,
		clusters []Cluster,
		values map[string]any,
		charts map[string]helmv2.HelmChartTemplateSpec,
		vpnKeyGen VPNAPIClient,
	) (EnvAppRendered, error)
}

type cueApp struct {
	name        string
	description string
	icon        template.HTML
	namespace   string
	schema      Schema
	cfg         cue.Value
	data        CueAppData
}

type CueAppData map[string][]byte

func ParseCueAppConfig(data CueAppData) (cue.Value, error) {
	ctx := cuecontext.New()
	buildCtx := build.NewContext()
	cfg := &load.Config{
		Context: buildCtx,
		Overlay: map[string]load.Source{},
	}
	names := make([]string, 0)
	for n, b := range data {
		a := fmt.Sprintf("/%s", n)
		names = append(names, a)
		cfg.Overlay[a] = load.FromString("package main\n\n" + string(b))
	}
	instances := load.Instances(names, cfg)
	for _, inst := range instances {
		if inst.Err != nil {
			return cue.Value{}, inst.Err
		}
	}
	if len(instances) != 1 {
		return cue.Value{}, fmt.Errorf("invalid")
	}
	ret := ctx.BuildInstance(instances[0])
	if err := ret.Err(); err != nil {
		return cue.Value{}, err
	}
	if err := ret.Validate(); err != nil {
		return cue.Value{}, err
	}
	return ret, nil
}

func newCueApp(config cue.Value, data CueAppData) (cueApp, error) {
	cfg := struct {
		Name        string `json:"name"`
		Namespace   string `json:"namespace"`
		Description string `json:"description"`
		Icon        string `json:"icon"`
	}{}
	if err := config.Decode(&cfg); err != nil {
		return cueApp{}, err
	}
	schema, err := NewCueSchema("input", config.LookupPath(cue.ParsePath("input")))
	if err != nil {
		return cueApp{}, err
	}
	return cueApp{
		name:        cfg.Name,
		description: cfg.Description,
		icon:        template.HTML(cfg.Icon),
		namespace:   cfg.Namespace,
		schema:      schema,
		cfg:         config,
		data:        data,
	}, nil
}

func ParseAndCreateNewCueApp(data CueAppData) (cueApp, error) {
	config, err := ParseCueAppConfig(data)
	if err != nil {
		return cueApp{}, fmt.Errorf(errors.Details(err, nil))
	}
	if err := config.Err(); err != nil {
		return cueApp{}, fmt.Errorf(errors.Details(err, nil))
	}
	return newCueApp(config, data)
}

func (a cueApp) Name() string {
	return a.name
}

func (a cueApp) Slug() string {
	return strings.ReplaceAll(strings.ToLower(a.name), " ", "-")
}

func (a cueApp) Description() string {
	return a.description
}

func (a cueApp) Icon() template.HTML {
	return a.icon
}

func (a cueApp) Schema() Schema {
	return a.schema
}

func (a cueApp) Namespace() string {
	return a.namespace
}

func (a cueApp) render(values map[string]any) (rendered, error) {
	ret := rendered{
		Name:      a.Slug(),
		Resources: make(CueAppData),
		HelmCharts: HelmCharts{
			Git: make(map[string]HelmChartGitRepo),
		},
		ContainerImages: make(map[string]ContainerImage),
		Ports:           make([]PortForward, 0),
		Data:            a.data,
	}
	var buf bytes.Buffer
	if err := json.NewEncoder(&buf).Encode(values); err != nil {
		return rendered{}, err
	}
	ctx := a.cfg.Context()
	d := ctx.CompileBytes(buf.Bytes())
	res := a.cfg.Unify(d).Eval()
	if err := res.Err(); err != nil {
		return rendered{}, fmt.Errorf(errors.Details(err, nil))
	}
	if err := res.Validate(); err != nil {
		return rendered{}, err
	}
	full, err := json.MarshalIndent(res, "", "\t")
	if err != nil {
		return rendered{}, err
	}
	ret.Raw = full
	ret.Data["rendered.json"] = full
	readme, err := res.LookupPath(cue.ParsePath("readme")).String()
	if err != nil {
		return rendered{}, err
	}
	ret.Readme = readme
	res.LookupPath(cue.ParsePath("input.cluster.name")).Decode(&ret.Cluster)
	if err := res.LookupPath(cue.ParsePath("output.clusterProxy")).Decode(&ret.ClusterProxies); err != nil {
		return rendered{}, err
	}
	if err := res.LookupPath(cue.ParsePath("namespaces")).Decode(&ret.Namespaces); err != nil {
		return rendered{}, err
	}
	if err := res.LookupPath(cue.ParsePath("output.openPort")).Decode(&ret.Ports); err != nil {
		return rendered{}, err
	}
	{
		envVars := []string{}
		if err := res.LookupPath(cue.ParsePath("envVars")).Decode(&envVars); err != nil {
			return rendered{}, err
		}
		for _, ev := range envVars {
			items := strings.SplitN(ev, "=", 2)
			if len(items) != 2 {
				panic(ev)
			}
			ret.EnvVars = append(ret.EnvVars, EnvVar{items[0], items[1]})
		}
	}
	{
		charts := res.LookupPath(cue.ParsePath("output.charts"))
		i, err := charts.Fields()
		if err != nil {
			return rendered{}, err
		}
		for i.Next() {
			var chartRef helmChartRef
			if err := i.Value().Decode(&chartRef); err != nil {
				return rendered{}, err
			}
			if chartRef.Kind == "GitRepository" {
				var chart HelmChartGitRepo
				if err := i.Value().Decode(&chart); err != nil {
					return rendered{}, err
				}
				ret.HelmCharts.Git[cleanName(i.Selector().String())] = chart
			}
		}
	}
	{
		images := res.LookupPath(cue.ParsePath("output.images"))
		i, err := images.Fields()
		if err != nil {
			return rendered{}, err
		}
		for i.Next() {
			var img ContainerImage
			if err := i.Value().Decode(&img); err != nil {
				return rendered{}, err
			}
			ret.ContainerImages[cleanName(i.Selector().String())] = img
		}
	}
	{
		helm := res.LookupPath(cue.ParsePath("output.helm"))
		i, err := helm.Fields()
		if err != nil {
			return rendered{}, err
		}
		for i.Next() {
			if contents, err := cueyaml.Encode(i.Value()); err != nil {
				return rendered{}, err
			} else {
				name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
				ret.Resources[name] = contents
			}
		}
	}
	{
		resources := res.LookupPath(cue.ParsePath("resources"))
		i, err := resources.Fields()
		if err != nil {
			return rendered{}, err
		}
		for i.Next() {
			if contents, err := cueyaml.Encode(i.Value()); err != nil {
				return rendered{}, err
			} else {
				name := fmt.Sprintf("%s.yaml", cleanName(i.Selector().String()))
				ret.Resources[name] = contents
			}
		}
	}
	helpValue := res.LookupPath(cue.ParsePath("help"))
	if helpValue.Exists() {
		if err := helpValue.Decode(&ret.Help); err != nil {
			return rendered{}, err
		}
	}
	url, err := res.LookupPath(cue.ParsePath("url")).String()
	if err != nil {
		return rendered{}, err
	}
	ret.URL = url
	icon, err := res.LookupPath(cue.ParsePath("icon")).String()
	if err != nil {
		return rendered{}, err
	}
	ret.Icon = icon
	access, err := extractAccess(res.LookupPath(cue.ParsePath("outs")))
	if err != nil {
		return rendered{}, err
	}
	ret.Access = access
	return ret, nil
}

func extractAccessInternal(v cue.Value) ([]Access, error) {
	ret := []Access{}
	a := v.LookupPath(cue.ParsePath("access"))
	if err := a.Err(); err != nil {
		return nil, err
	}
	i, err := a.List()
	if err != nil {
		return nil, err
	}
	for i.Next() {
		n := i.Value().LookupPath(cue.ParsePath("name"))
		if err := n.Err(); err != nil {
			return nil, err
		}
		nn, err := n.String()
		if err != nil {
			return nil, err
		}
		t := i.Value().LookupPath(cue.ParsePath("type"))
		if err := t.Err(); err != nil {
			return nil, err
		}
		d, err := t.String()
		if err != nil {
			return nil, err
		}
		switch d {
		case "https":
			{
				var q AccessHTTPS
				if err := i.Value().Decode(&q); err != nil {
					return nil, err
				}
				ret = append(ret, Access{Type: "https", Name: nn, HTTPS: &q})
			}
		case "ssh":
			{
				var q AccessSSH
				if err := i.Value().Decode(&q); err != nil {
					return nil, err
				}
				ret = append(ret, Access{Type: "ssh", Name: nn, SSH: &q})
			}
		case "tcp":
			{
				var q AccessTCP
				if err := i.Value().Decode(&q); err != nil {
					return nil, err
				}
				ret = append(ret, Access{Type: "tcp", Name: nn, TCP: &q})
			}
		case "udp":
			{
				var q AccessUDP
				if err := i.Value().Decode(&q); err != nil {
					return nil, err
				}
				ret = append(ret, Access{Type: "udp", Name: nn, UDP: &q})
			}
		case "postgresql":
			{
				var q AccessPostgreSQL
				if err := i.Value().Decode(&q); err != nil {
					return nil, err
				}
				ret = append(ret, Access{Type: "postgresql", Name: nn, PostgreSQL: &q})
			}
		case "mongodb":
			{
				var q AccessMongoDB
				if err := i.Value().Decode(&q); err != nil {
					return nil, err
				}
				ret = append(ret, Access{Type: "mongodb", Name: nn, MongoDB: &q})
			}
		case "env_var":
			{
				var q AccessEnvVar
				if err := i.Value().Decode(&q); err != nil {
					return nil, err
				}
				ret = append(ret, Access{Type: "env_var", Name: nn, EnvVar: &q})
			}
		}
	}
	for _, sub := range []string{"ingress", "postgresql", "mongodb", "services", "vm"} {
		subout := v.LookupPath(cue.ParsePath(sub))
		if subout.Err() != nil {
			continue
		}
		if a, err := extractAccess(subout); err != nil {
			return nil, err
		} else {
			ret = append(ret, a...)
		}
	}
	return ret, nil
}

func extractAccess(v cue.Value) ([]Access, error) {
	if err := v.Err(); err != nil {
		return nil, err
	}
	i, err := v.Fields()
	if err != nil {
		return nil, err
	}
	ret := []Access{}
	for i.Next() {
		if a, err := extractAccessInternal(i.Value()); err != nil {
			return nil, fmt.Errorf(errors.Details(err, nil))
		} else {
			ret = append(ret, a...)
		}
	}
	return ret, nil
}

type cueEnvApp struct {
	cueApp
}

func NewCueEnvApp(data CueAppData) (EnvApp, error) {
	app, err := ParseAndCreateNewCueApp(data)
	if err != nil {
		return nil, err
	}
	return cueEnvApp{app}, nil
}

func NewDodoApp(appCfg []byte) (EnvApp, error) {
	return NewCueEnvApp(CueAppData{
		"app.cue":  appCfg,
		"base.cue": cueBaseConfig,
		"dodo.cue": dodoAppCue,
		"env.cue":  cueEnvAppGlobal,
	})
}

func (a cueEnvApp) Type() AppType {
	return AppTypeEnv
}

func merge(d map[string]any, v map[string]any) map[string]any {
	ret := map[string]any{}
	for k, val := range d {
		if vv, ok := v[k]; ok && vv != nil {
			if mv, ok := val.(map[string]any); ok {
				// TODO(gio): check that it is actually map
				mm, ok := vv.(map[string]any)
				if !ok {
					// TODO(gio): handle #Network and others
					ret[k] = vv
				} else {
					ret[k] = merge(mv, mm)
				}
			} else {
				ret[k] = vv
			}
		} else {
			ret[k] = val
		}
	}
	for k, v := range v {
		if v != nil {
			if _, ok := d[k]; !ok {
				ret[k] = v
			}
		}
	}
	return ret
}

func (a cueEnvApp) Render(
	release Release,
	env EnvConfig,
	networks []Network,
	clusters []Cluster,
	values map[string]any,
	charts map[string]helmv2.HelmChartTemplateSpec,
	vpnKeyGen VPNAPIClient,
) (EnvAppRendered, error) {
	dv, err := ExtractDefaultValues(a.cueApp.cfg.LookupPath(cue.ParsePath("input")))
	if err != nil {
		return EnvAppRendered{}, err
	}
	if dv == nil {
		dv = map[string]any{}
	}
	mv := merge(dv.(map[string]any), values)
	derived, err := deriveValues(mv, mv, a.Schema(), networks, clusters, vpnKeyGen)
	if err != nil {
		return EnvAppRendered{}, err
	}
	if charts == nil {
		charts = make(map[string]helmv2.HelmChartTemplateSpec)
	}
	if clusters == nil {
		clusters = []Cluster{}
	}
	ret, err := a.cueApp.render(map[string]any{
		"global":      env,
		"release":     release,
		"input":       derived,
		"localCharts": charts,
		"networks":    NetworkMap(networks),
		"clusters":    clusters,
	})
	if err != nil {
		return EnvAppRendered{}, err
	}
	return EnvAppRendered{
		rendered: ret,
		Config: AppInstanceConfig{
			AppId:   a.Slug(),
			Env:     env,
			Release: release,
			Values:  values,
			Input:   derived,
			URL:     ret.URL,
			Help:    ret.Help,
			Icon:    ret.Icon,
		},
	}, nil
}

type cueInfraApp struct {
	cueApp
}

func NewCueInfraApp(data CueAppData) (InfraApp, error) {
	app, err := ParseAndCreateNewCueApp(data)
	if err != nil {
		return nil, err
	}
	return cueInfraApp{app}, nil
}

func (a cueInfraApp) Type() AppType {
	return AppTypeInfra
}

func (a cueInfraApp) Render(release Release, infra InfraConfig, networks []InfraNetwork, values map[string]any, charts map[string]helmv2.HelmChartTemplateSpec) (InfraAppRendered, error) {
	if charts == nil {
		charts = make(map[string]helmv2.HelmChartTemplateSpec)
	}
	ret, err := a.cueApp.render(map[string]any{
		"global":      infra,
		"release":     release,
		"input":       values,
		"localCharts": charts,
		"networks":    InfraNetworkMap(networks),
	})
	if err != nil {
		return InfraAppRendered{}, err
	}
	return InfraAppRendered{
		rendered: ret,
		Config: InfraAppInstanceConfig{
			AppId:   a.Slug(),
			Infra:   infra,
			Release: release,
			Values:  values,
			Input:   values,
			URL:     ret.URL,
			Help:    ret.Help,
		},
	}, nil
}

func cleanName(s string) string {
	return strings.ReplaceAll(strings.ReplaceAll(s, "\"", ""), "'", "")
}

func join[T fmt.Stringer](items []T, sep string) string {
	var tmp []string
	for _, i := range items {
		tmp = append(tmp, i.String())
	}
	return strings.Join(tmp, ",")
}

func NetworkMap(networks []Network) map[string]Network {
	ret := make(map[string]Network)
	for _, n := range networks {
		ret[strings.ToLower(n.Name)] = n
	}
	return ret
}

func InfraNetworkMap(networks []InfraNetwork) map[string]InfraNetwork {
	ret := make(map[string]InfraNetwork)
	for _, n := range networks {
		ret[strings.ToLower(n.Name)] = n
	}
	return ret
}
