// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

package vm

import (
	"bytes"
	"context"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
	"net"
	"strconv"
	"strings"
	"text/template"

	"github.com/containernetworking/cni/libcni"
	"github.com/containernetworking/plugins/pkg/testutils"
	"github.com/google/uuid"
	"github.com/jsimonetti/rtnetlink"
	talosnet "github.com/talos-systems/net"

	"github.com/talos-systems/talos/pkg/provision"
)

// CreateNetwork builds bridge interface name by taking part of checksum of the network name
// so that interface name is defined by network name, and different networks have
// different bridge interfaces.
//
//nolint:gocyclo
func (p *Provisioner) CreateNetwork(ctx context.Context, state *State, network provision.NetworkRequest) error {
	networkNameHash := sha256.Sum256([]byte(network.Name))
	state.BridgeName = fmt.Sprintf("%s%s", "talos", hex.EncodeToString(networkNameHash[:])[:8])

	// bring up the bridge interface for the first time to get gateway IP assigned
	t := template.Must(template.New("bridge").Parse(bridgeTemplate))

	var buf bytes.Buffer

	err := t.Execute(&buf, struct {
		NetworkName   string
		InterfaceName string
		MTU           string
	}{
		NetworkName:   network.Name,
		InterfaceName: state.BridgeName,
		MTU:           strconv.Itoa(network.MTU),
	})
	if err != nil {
		return fmt.Errorf("error templating bridge CNI config: %w", err)
	}

	bridgeConfig, err := libcni.ConfFromBytes(buf.Bytes())
	if err != nil {
		return fmt.Errorf("error parsing bridge CNI config: %w", err)
	}

	cniConfig := libcni.NewCNIConfigWithCacheDir(network.CNI.BinPath, network.CNI.CacheDir, nil)

	ns, err := testutils.NewNS()
	if err != nil {
		return err
	}

	defer func() {
		ns.Close()              //nolint:errcheck
		testutils.UnmountNS(ns) //nolint:errcheck
	}()

	// pick a fake address to use for provisioning an interface
	fakeIPs := make([]string, len(network.CIDRs))
	for j := range fakeIPs {
		var fakeIP net.IP

		fakeIP, err = talosnet.NthIPInNetwork(&network.CIDRs[j], 2)
		if err != nil {
			return err
		}

		fakeIPs[j] = talosnet.FormatCIDR(fakeIP, network.CIDRs[j])
	}

	gatewayAddrs := make([]string, len(network.GatewayAddrs))
	for j := range gatewayAddrs {
		gatewayAddrs[j] = network.GatewayAddrs[j].String()
	}

	containerID := uuid.New().String()
	runtimeConf := libcni.RuntimeConf{
		ContainerID: containerID,
		NetNS:       ns.Path(),
		IfName:      "veth0",
		Args: [][2]string{
			{"IP", strings.Join(fakeIPs, ",")},
			{"GATEWAY", strings.Join(gatewayAddrs, ",")},
		},
	}

	_, err = cniConfig.AddNetwork(ctx, bridgeConfig, &runtimeConf)
	if err != nil {
		return fmt.Errorf("error provisioning bridge CNI network: %w", err)
	}

	err = cniConfig.DelNetwork(ctx, bridgeConfig, &runtimeConf)
	if err != nil {
		return fmt.Errorf("error deleting bridge CNI network: %w", err)
	}

	// prepare an actual network config to be used by the VMs
	t = template.Must(template.New("network").Parse(networkTemplate))

	buf.Reset()

	err = t.Execute(&buf, struct {
		NetworkName   string
		InterfaceName string
		MTU           string
	}{
		NetworkName:   network.Name,
		InterfaceName: state.BridgeName,
		MTU:           strconv.Itoa(network.MTU),
	})
	if err != nil {
		return fmt.Errorf("error templating VM CNI config: %w", err)
	}

	if state.VMCNIConfig, err = libcni.ConfListFromBytes(buf.Bytes()); err != nil {
		return fmt.Errorf("error parsing VM CNI config: %w", err)
	}

	return nil
}

// DestroyNetwork destroy bridge interface by name to clean up.
func (p *Provisioner) DestroyNetwork(state *State) error {
	iface, err := net.InterfaceByName(state.BridgeName)
	if err != nil {
		return fmt.Errorf("error looking up bridge interface %q: %w", state.BridgeName, err)
	}

	rtconn, err := rtnetlink.Dial(nil)
	if err != nil {
		return fmt.Errorf("error dialing rnetlink: %w", err)
	}

	if err = rtconn.Link.Delete(uint32(iface.Index)); err != nil {
		return fmt.Errorf("error deleting bridge interface: %w", err)
	}

	return nil
}

const bridgeTemplate = `
{
	"name": "{{ .NetworkName }}",
	"cniVersion": "0.4.0",
	"type": "bridge",
	"bridge": "{{ .InterfaceName }}",
	"ipMasq": true,
	"isGateway": true,
	"isDefaultGateway": true,
	"ipam": {
		  "type": "static"
	},
	"mtu": {{ .MTU }}
}
`

const networkTemplate = `
{
	"name": "{{ .NetworkName }}",
	"cniVersion": "0.4.0",
	"plugins": [
		{
			"type": "bridge",
			"bridge": "{{ .InterfaceName }}",
			"ipMasq": true,
			"isGateway": true,
			"isDefaultGateway": true,
			"ipam": {
				"type": "static"
			},
			"mtu": {{ .MTU }}
		},
		{
			"type": "firewall"
		},
		{
			"type": "tc-redirect-tap"
		}
	]
}
`
