package route

import (
	"net/netip"

	"github.com/netbirdio/netbird/management/server/status"
)

// Windows has some limitation regarding metric size that differ from Unix-like systems.
// Because of that we are limiting the min and max metric size based on Windows limits:
// see based on info from https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/route_ws2008
const (
	// MinMetric max metric input
	MinMetric = 1
	// MaxMetric max metric input
	MaxMetric = 9999
	// MaxNetIDChar Max Network Identifier
	MaxNetIDChar = 40
)

const (
	// InvalidNetworkString invalid network type string
	InvalidNetworkString = "Invalid"
	// IPv4NetworkString IPv4 network type string
	IPv4NetworkString = "IPv4"
	// IPv6NetworkString IPv6 network type string
	IPv6NetworkString = "IPv6"
)

const (
	// InvalidNetwork invalid network type
	InvalidNetwork NetworkType = iota
	// IPv4Network IPv4 network type
	IPv4Network
	// IPv6Network IPv6 network type
	IPv6Network
)

// NetworkType route network type
type NetworkType int

// String returns prefix type string
func (p NetworkType) String() string {
	switch p {
	case IPv4Network:
		return IPv4NetworkString
	case IPv6Network:
		return IPv6NetworkString
	default:
		return InvalidNetworkString
	}
}

// ToPrefixType returns a prefix type
func ToPrefixType(prefix string) NetworkType {
	switch prefix {
	case IPv4NetworkString:
		return IPv4Network
	case IPv6NetworkString:
		return IPv6Network
	default:
		return InvalidNetwork
	}
}

// Route represents a route
type Route struct {
	ID string `gorm:"primaryKey"`
	// AccountID is a reference to Account that this object belongs
	AccountID   string       `gorm:"index"`
	Network     netip.Prefix `gorm:"serializer:gob"`
	NetID       string
	Description string
	Peer        string
	PeerGroups  []string `gorm:"serializer:gob"`
	NetworkType NetworkType
	Masquerade  bool
	Metric      int
	Enabled     bool
	Groups      []string `gorm:"serializer:json"`
}

// EventMeta returns activity event meta related to the route
func (r *Route) EventMeta() map[string]any {
	return map[string]any{"name": r.NetID, "network_range": r.Network.String(), "peer_id": r.Peer, "peer_groups": r.PeerGroups}
}

// Copy copies a route object
func (r *Route) Copy() *Route {
	route := &Route{
		ID:          r.ID,
		Description: r.Description,
		NetID:       r.NetID,
		Network:     r.Network,
		NetworkType: r.NetworkType,
		Peer:        r.Peer,
		PeerGroups:  make([]string, len(r.PeerGroups)),
		Metric:      r.Metric,
		Masquerade:  r.Masquerade,
		Enabled:     r.Enabled,
		Groups:      make([]string, len(r.Groups)),
	}
	copy(route.Groups, r.Groups)
	copy(route.PeerGroups, r.PeerGroups)
	return route
}

// IsEqual compares one route with the other
func (r *Route) IsEqual(other *Route) bool {
	return other.ID == r.ID &&
		other.Description == r.Description &&
		other.NetID == r.NetID &&
		other.Network == r.Network &&
		other.NetworkType == r.NetworkType &&
		other.Peer == r.Peer &&
		other.Metric == r.Metric &&
		other.Masquerade == r.Masquerade &&
		other.Enabled == r.Enabled &&
		compareList(r.Groups, other.Groups) &&
		compareList(r.PeerGroups, other.PeerGroups)
}

// ParseNetwork Parses a network prefix string and returns a netip.Prefix object and if is invalid, IPv4 or IPv6
func ParseNetwork(networkString string) (NetworkType, netip.Prefix, error) {
	prefix, err := netip.ParsePrefix(networkString)
	if err != nil {
		return InvalidNetwork, netip.Prefix{}, status.Errorf(status.InvalidArgument, "invalid network %s", networkString)
	}

	masked := prefix.Masked()

	if !masked.IsValid() {
		return InvalidNetwork, netip.Prefix{}, status.Errorf(status.InvalidArgument, "invalid range %s", networkString)
	}

	if masked.Addr().Is6() {
		return IPv6Network, masked, nil
	}

	return IPv4Network, masked, nil
}

func compareList(list, other []string) bool {
	if len(list) != len(other) {
		return false
	}
	for _, id := range list {
		match := false
		for _, otherID := range other {
			if id == otherID {
				match = true
				break
			}
		}
		if !match {
			return false
		}
	}

	return true
}

// GetHAUniqueID returns a highly available route ID by combining Network ID and Network range address
func GetHAUniqueID(input *Route) string {
	return input.NetID + "-" + input.Network.String()
}
