package config

import (
	_ "embed"
	"encoding/json"
	"fmt"
	"net"
	"os"
	"path/filepath"
	"strings"

	"git.cryptic.systems/volker.raschek/dyndns-client/pkg/types"
	log "github.com/sirupsen/logrus"
)

//go:embed config.json
var defaultConfig string

// GetDefaultConfiguration returns a default configuration
func GetDefaultConfiguration() (*types.Config, error) {
	cnf := new(types.Config)
	jsonDecoder := json.NewDecoder(strings.NewReader(defaultConfig))

	err := jsonDecoder.Decode(cnf)
	if err != nil {
		return nil, fmt.Errorf("failed to decode default config: %w", err)
	}

	defaultInterface, err := getDefaultInterfaceByIP()
	if err != nil {
		return nil, err
	}
	cnf.Ifaces = []string{defaultInterface.Name}

	return cnf, nil
}

// Read config from a file
func Read(cnfFile string) (*types.Config, error) {
	// Load burned in configuration if config not available
	if _, err := os.Stat(cnfFile); os.IsNotExist(err) {
		// #nosec G301
		if err := os.MkdirAll(filepath.Dir(cnfFile), 0755); err != nil {
			return nil, fmt.Errorf("failed to create directory: %w", err)
		}
		cnf, err := GetDefaultConfiguration()
		if err != nil {
			return nil, err
		}

		err = cnf.Validate()
		if err != nil {
			return nil, err
		}

		log.Infof("use embedded configuration")

		return cnf, nil
	}

	// #nosec G304
	f, err := os.Open(cnfFile)
	if err != nil {
		return nil, fmt.Errorf("failed to open file: %w", err)
	}
	defer func() { _ = f.Close() }()

	cnf := new(types.Config)
	jsonDecoder := json.NewDecoder(f)
	err = jsonDecoder.Decode(cnf)
	if err != nil {
		return nil, fmt.Errorf("failed to decode json: %w", err)
	}

	if len(cnf.Ifaces) <= 0 {
		defaultInterface, err := getDefaultInterfaceByIP()
		if err != nil {
			return nil, err
		}
		cnf.Ifaces = []string{defaultInterface.Name}
	}

	for _, iface := range cnf.Ifaces {
		if _, err := net.InterfaceByName(iface); err != nil {
			return nil, fmt.Errorf("unknown interface: %v", iface)
		}
	}

	err = cnf.Validate()
	if err != nil {
		return nil, err
	}

	log.Infof("use configuration from file %v", cnfFile)

	return cnf, nil
}

// Write config into a file
func Write(cnf *types.Config, cnfFile string) error {
	if _, err := os.Stat(filepath.Dir(cnfFile)); os.IsNotExist(err) {
		// #nosec G301
		err := os.MkdirAll(filepath.Dir(cnfFile), 0755)
		if err != nil {
			return err
		}
	}

	// #nosec G304
	f, err := os.Create(cnfFile)
	if err != nil {
		return fmt.Errorf("failed to create file %v: %v", cnfFile, err)
	}
	defer func() { _ = f.Close() }()

	jsonEncoder := json.NewEncoder(f)
	jsonEncoder.SetIndent("", "  ")
	err = jsonEncoder.Encode(cnf)
	if err != nil {
		return fmt.Errorf("failed to encode json: %w", err)
	}
	return nil
}

func getDefaultInterfaceByIP() (*net.Interface, error) {
	ifaces, err := net.Interfaces()
	if err != nil {
		return nil, fmt.Errorf("failed to fet network interfaces from kernel: %w", err)
	}

	defaultIP := getOutboundIP()

	for _, iface := range ifaces {
		addrs, err := iface.Addrs()
		if err != nil {
			return nil, fmt.Errorf("failed to list ip addresses for interface %v: %w", iface.Name, err)
		}

		for _, addr := range addrs {
			addrIP := strings.Split(addr.String(), "/")[0]
			if addrIP == defaultIP.String() {
				return &iface, nil
			}
		}
	}

	return nil, fmt.Errorf("no interface found fo ip address %v", defaultIP)
}

func getOutboundIP() net.IP {
	conn, err := net.Dial("udp", "8.8.8.8:80")
	if err != nil {
		log.Fatal(err)
	}
	defer conn.Close()
	localAddr := conn.LocalAddr().(*net.UDPAddr)
	return localAddr.IP
}