package service

import (
	"encoding/json"
	"fmt"

	"github.com/mhsanaei/3x-ui/v3/internal/amneziawg"
	"github.com/mhsanaei/3x-ui/v3/internal/database/model"
	"github.com/mhsanaei/3x-ui/v3/internal/util/common"
	wgutil "github.com/mhsanaei/3x-ui/v3/internal/util/wireguard"
)

// defaultAmneziaWGSubnetBases resolves the /CIDR bases new peer addresses are
// allocated from, out of the inbound's own configured server subnet(s) —
// unlike WireGuard, which always falls back to a fixed 10.0.0.0/24. v6Base is
// "" when the server doesn't have IPv6 enabled.
func defaultAmneziaWGSubnetBases(settingsJSON string) (v4Base, v6Base string, err error) {
	var parsed amneziawg.InboundSettings
	if err := json.Unmarshal([]byte(settingsJSON), &parsed); err != nil {
		return "", "", fmt.Errorf("amneziawg: invalid settings: %w", err)
	}
	if parsed.Server == nil {
		return "", "", fmt.Errorf("amneziawg: settings missing server block")
	}
	cidr := parsed.Server.SubnetCIDR
	if cidr <= 0 {
		cidr = 24
	}
	v4Base = fmt.Sprintf("%s/%d", parsed.Server.SubnetIP, cidr)
	if parsed.Server.IPv6Enabled && parsed.Server.IPv6Subnet != "" {
		v6Base = parsed.Server.IPv6Subnet
	}
	return v4Base, v6Base, nil
}

// defaultAmneziaWGClients fills in blank credentials and a free tunnel address
// for new clients, mutating both the typed clients and the parallel raw maps
// persisted into the settings. Existing values are never overwritten, so an
// edit never rotates keys. Mirrors defaultWireguardClients; crossInboundUsed
// (see otherTunnelAllowedIPs) narrows which addresses are still free.
func defaultAmneziaWGClients(settingsJSON string, existing, clients []model.Client, interfaceClients []any, crossInboundUsed map[string]string) error {
	v4Base, v6Base, err := defaultAmneziaWGSubnetBases(settingsJSON)
	if err != nil {
		return err
	}

	used := make([]string, 0)
	for i := range existing {
		used = append(used, existing[i].AllowedIPs...)
	}
	for addr := range crossInboundUsed {
		used = append(used, addr)
	}
	for i := range clients {
		c := &clients[i]
		if c.PrivateKey == "" && c.PublicKey == "" {
			priv, pub, err := wgutil.GenerateWireguardKeypair()
			if err != nil {
				return err
			}
			c.PrivateKey = priv
			c.PublicKey = pub
		} else if c.PublicKey == "" && c.PrivateKey != "" {
			pub, err := wgutil.PublicKeyFromPrivate(c.PrivateKey)
			if err != nil {
				return err
			}
			c.PublicKey = pub
		}
		if len(c.AllowedIPs) == 0 {
			// allowWidening=false: unlike WireGuard's Xray-native inbound,
			// AmneziaWG's kernel interface Address is exactly the configured
			// subnet, so an address allocated outside it would be silently
			// unroutable. Exhaustion here must fail loudly instead.
			addr, err := allocateWireguardAddress(used, v4Base, false)
			if err != nil {
				return err
			}
			allowed := []string{addr}
			if v6Base != "" {
				addr6, err := allocateWireguardAddress(used, v6Base, false)
				if err != nil {
					return err
				}
				allowed = append(allowed, addr6)
			}
			c.AllowedIPs = allowed
		} else {
			normalized, err := normalizeWireguardAllowedIPs(c.AllowedIPs)
			if err != nil {
				return err
			}
			if len(normalized) == 0 {
				return common.NewError("amneziawg: allowedIPs has no usable entry")
			}
			if hit := wireguardAllowedIPsCollision(normalized, used); hit != "" {
				if where := crossInboundUsed[hit]; where != "" {
					return common.NewError("amneziawg: allowedIPs entry", hit, "is already used by a client on", where)
				}
				return common.NewError("amneziawg: allowedIPs entry already used by another client:", hit)
			}
			c.AllowedIPs = normalized
		}
		used = append(used, c.AllowedIPs...)

		if i < len(interfaceClients) {
			if m, ok := interfaceClients[i].(map[string]any); ok {
				m["privateKey"] = c.PrivateKey
				m["publicKey"] = c.PublicKey
				m["allowedIPs"] = c.AllowedIPs
				if c.PreSharedKey != "" {
					m["preSharedKey"] = c.PreSharedKey
				}
				interfaceClients[i] = m
			}
		}
	}
	return nil
}
