// 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 address

import (
	"context"
	"net"
	"time"

	"golang.org/x/sys/unix"

	"github.com/talos-systems/talos/pkg/machinery/config"
)

const staticRouteDefaultMetric uint32 = 10

// Static implements the Addressing interface.
type Static struct {
	CIDR        string
	Mtu         int
	FQDN        string
	RouteList   []config.Route
	NetIf       *net.Interface
	NameServers []net.IP
}

// Discover doesnt do anything in the static configuration since all
// the necessary configuration data is supplied via config.
func (s *Static) Discover(ctx context.Context, link *net.Interface) error {
	s.NetIf = link

	return nil
}

// Name returns back the name of the address method.
func (s *Static) Name() string {
	return "static"
}

// Address returns the IP address.
func (s *Static) Address() *net.IPNet {
	var ip net.IP

	var ipn *net.IPNet

	if s.CIDR != "" {
		//nolint:errcheck
		ip, ipn, _ = net.ParseCIDR(s.CIDR)
		ipn.IP = ip
	}

	return ipn
}

// Mask returns the netmask.
func (s *Static) Mask() net.IPMask {
	//nolint:errcheck
	_, ipnet, _ := net.ParseCIDR(s.CIDR)

	return ipnet.Mask
}

// MTU returns the specified MTU.
func (s *Static) MTU() uint32 {
	mtu := uint32(s.Mtu)
	if mtu == 0 {
		mtu = uint32(s.NetIf.MTU)
	}

	return mtu
}

// TTL returns the address lifetime. Since this is static, there is
// no TTL (0).
func (s *Static) TTL() time.Duration {
	return 0
}

// Family qualifies the address as ipv4 or ipv6.
func (s *Static) Family() int {
	if s.Address() == nil {
		panic("unable to determine address family as address is nil")
	}

	if s.Address().IP.To4() != nil {
		return unix.AF_INET
	}

	return unix.AF_INET6
}

// Scope sets the address scope.
func (s *Static) Scope() uint8 {
	return unix.RT_SCOPE_UNIVERSE
}

// Routes aggregates the specified routes for a given device configuration
// TODO: do we need to be explicit on route vs gateway?
func (s *Static) Routes() (routes []*Route) {
	for _, route := range s.RouteList {
		_, ipnet, err := net.ParseCIDR(route.Network())
		if err != nil {
			// TODO: we should at least log the error
			continue
		}

		metric := staticRouteDefaultMetric

		if route.Metric() != 0 {
			metric = route.Metric()
		}

		routes = append(routes, &Route{
			Destination: ipnet,
			Gateway:     net.ParseIP(route.Gateway()),
			Metric:      metric,
		})
	}

	return routes
}

// Resolvers returns the DNS resolvers.
func (s *Static) Resolvers() []net.IP {
	return s.NameServers
}

// Hostname returns the hostname.
func (s *Static) Hostname() string {
	return s.FQDN
}

// Link returns the underlying net.Interface that this address
// method is configured for.
func (s Static) Link() *net.Interface {
	return s.NetIf
}

// Valid denotes if this address method should be used.
func (s *Static) Valid() bool {
	return true
}
