// Package amneziawg holds the AmneziaWG protocol's shared, DB-backed shapes
// (Instance, Peer, Obfuscation31, ServerSettings/InboundSettings) and the
// pure functions that derive an Instance from a stored inbound row. It no
// longer manages any OS-level interface itself: that was the kernel-module
// (DKMS) + awg-quick + TPROXY architecture this fork shipped originally,
// retired in favor of an embedded, pure-Go one (amneziawg-go over a gVisor
// netstack, see internal/amneziawgnet) in a hard cutover. This package's
// remaining code is deliberately protocol-shape-only, with no OS dependency
// at all, so both the (now-removed) kernel-module path and the embedded
// path could read -- and, historically, did read -- it identically.
package amneziawg

import (
	"encoding/json"
	"fmt"
	"net/netip"

	"github.com/mhsanaei/3x-ui/v3/internal/database/model"
)

// InstanceFromInbound derives a desired Instance from an AmneziaWG inbound,
// building one peer per active client. Returns false when the inbound is not
// a usable AmneziaWG inbound (wrong protocol, unparseable settings, or no
// server block) or has no enabled peer to serve — mirroring
// mtproto.InstanceFromInbound, which skips the sidecar entirely rather than
// run it with nothing to serve.
func InstanceFromInbound(ib *model.Inbound) (Instance, bool) {
	if ib == nil || ib.Protocol != model.AmneziaWG {
		return Instance{}, false
	}
	var parsed InboundSettings
	if err := json.Unmarshal([]byte(ib.Settings), &parsed); err != nil || parsed.Server == nil {
		return Instance{}, false
	}
	server := parsed.Server

	peers := make([]Peer, 0, len(parsed.Clients))
	for _, c := range parsed.Clients {
		if !c.Enable || c.PublicKey == "" || len(c.AllowedIPs) == 0 {
			continue
		}
		peers = append(peers, Peer{
			Email:          c.Email,
			PublicKey:      c.PublicKey,
			PresharedKey:   c.PreSharedKey,
			AllowedIPs:     c.AllowedIPs,
			ForwardedPorts: c.ForwardedPorts,
		})
	}
	if len(peers) == 0 {
		return Instance{}, false
	}

	addresses := []string{serverAddress(server.SubnetIP, server.SubnetCIDR)}
	if server.IPv6Enabled {
		if v6, ok := serverAddressV6(server.IPv6Subnet); ok {
			addresses = append(addresses, v6)
		}
	}

	return Instance{
		Id:                    ib.Id,
		Tag:                   ib.Tag,
		InterfaceName:         interfaceNameForID(ib.Id),
		ListenPort:            ib.Port,
		PrivateKey:            server.PrivateKey,
		PublicKey:             server.PublicKey,
		Address:               addresses,
		MTU:                   server.MTU,
		Obfuscation:           server.Obfuscation(),
		Peers:                 peers,
		ExternalInterface:     server.ExternalInterface,
		IPv6Enabled:           server.IPv6Enabled,
		IPv6ExternalInterface: server.IPv6ExternalInterface,
		RouteThroughXray:      server.RouteThroughXray,
	}, true
}

// interfaceNameForID derives the OS-level interface name for an inbound, e.g.
// "awg42". Kept even though the embedded path has no real kernel interface
// of its own: internal/amneziawgnet still uses the same name as a purely
// cosmetic/log-friendly label, so an existing peer's identity/history
// doesn't shift across the cutover.
func interfaceNameForID(id int) string {
	return fmt.Sprintf("awg%d", id)
}

// serverAddress returns the server's own tunnel address for a subnet base,
// e.g. "10.8.1.1/24" for base "10.8.1.0" or "10.8.1.5". The server always
// holds the first usable host of the network subnetIP/cidr actually
// describes -- derived via netip rather than assuming subnetIP already ends
// in ".0", so a subnetIP that isn't a bare network address (a typo, or a
// manually edited value) can never collide with peer addresses, which are
// allocated starting from the network's second host upward (see
// allocateWireguardAddress). Falls back to the previous literal behavior
// only if subnetIP/cidr doesn't parse as an IPv4 network at all -- normal
// saves never reach that path since ValidateSubnetIPv4 already rejects it.
func serverAddress(subnetIP string, cidr int) string {
	if cidr <= 0 {
		cidr = 24
	}
	// A /32 has no host bits at all -- "first usable host" is meaningless,
	// and Next() would step outside the block entirely -- so a single-host
	// base is used exactly as given, same as before this fix.
	prefix, err := netip.ParsePrefix(fmt.Sprintf("%s/%d", subnetIP, cidr))
	if err != nil || !prefix.Addr().Is4() || cidr >= 32 {
		return fmt.Sprintf("%s/%d", subnetIP, cidr)
	}
	host := prefix.Masked().Addr().Next()
	return fmt.Sprintf("%s/%d", host, cidr)
}

// serverAddressV6 returns the server's own IPv6 tunnel address for a subnet
// CIDR (e.g. "fd86:ea04:1115::1/64" for "fd86:ea04:1115::/64"), the first
// usable host in the prefix. ok is false when subnetCIDR is empty or not a
// valid IPv6 prefix.
func serverAddressV6(subnetCIDR string) (addr string, ok bool) {
	prefix, err := netip.ParsePrefix(subnetCIDR)
	if err != nil || !prefix.Addr().Is6() {
		return "", false
	}
	host := prefix.Masked().Addr().Next()
	return fmt.Sprintf("%s/%d", host, prefix.Bits()), true
}

// FirstIPv4 returns the first IPv4 address (mask stripped) among allowedIPs,
// or "" if none — used to derive a peer's tunnel IPv4 address.
func FirstIPv4(allowedIPs []string) string {
	for _, a := range allowedIPs {
		if prefix, err := netip.ParsePrefix(a); err == nil {
			if prefix.Addr().Is4() {
				return prefix.Addr().String()
			}
			continue
		}
		if addr, err := netip.ParseAddr(a); err == nil && addr.Is4() {
			return addr.String()
		}
	}
	return ""
}

// FirstIPv6 returns the first IPv6 address (mask stripped) among allowedIPs,
// or "" if none — the IPv6 counterpart of FirstIPv4, used by
// internal/amneziawgnet's IPv6-address-alias mechanism to find which
// address, if any, a peer wants aliased onto the host, and by
// internal/web/service/xray.go's injectAmneziawgV6Egress to build that
// peer's own freedom outbound (sendThrough). Only the first match is
// returned, exactly like FirstIPv4 — more than one IPv6 AllowedIPs entry
// per peer is not a supported configuration for either feature.
func FirstIPv6(allowedIPs []string) string {
	for _, a := range allowedIPs {
		if prefix, err := netip.ParsePrefix(a); err == nil {
			if prefix.Addr().Is6() && !prefix.Addr().Is4In6() {
				return prefix.Addr().String()
			}
			continue
		}
		if addr, err := netip.ParseAddr(a); err == nil && addr.Is6() && !addr.Is4In6() {
			return addr.String()
		}
	}
	return ""
}
