package idgenerator

import (
	"crypto/rand"
	"fmt"
	"net"
	"os"
	"sync"

	"github.com/sony/sonyflake"
)

type sonyflakeIDImpl struct {
	sonyflake *sonyflake.Sonyflake
}

var sonyflakeInstanceCache sync.Map

const DefaultPodIPEnvKey = "POD_IP"
const AutoNodeID NodeID = -1

// NewSonyflakeID creates an IDGenerator backed by sonyflake.
//
// MachineID resolution order:
//  1. If nid is not AutoNodeID, nid is used directly as MachineID.
//  2. If nid is AutoNodeID, parse the given ip (or POD_IP when ip is empty)
//     and use the lower 16 bits of IPv4 as MachineID.
//  3. If both nid and ip are unavailable, generate a random uint16 as MachineID.
//
// Notes:
//   - nid must be in [0, 65535] when explicitly provided.
//   - ip must be a valid IPv4 address when used for derivation.
//   - this function panics when MachineID cannot be resolved or sonyflake init fails.
func NewSonyflakeID(nid NodeID, ip string) IDGenerator {
	machineID, err := resolveMachineID(nid, ip)
	if err != nil {
		panic(err)
	}

	if cached, ok := sonyflakeInstanceCache.Load(machineID); ok {
		return sonyflakeIDImpl{sonyflake: cached.(*sonyflake.Sonyflake)}
	}

	instance := sonyflake.NewSonyflake(sonyflake.Settings{MachineID: func() (uint16, error) { return machineID, nil }})
	if instance == nil {
		panic("failed to create sonyflake instance")
	}

	if cached, loaded := sonyflakeInstanceCache.LoadOrStore(machineID, instance); loaded {
		return sonyflakeIDImpl{sonyflake: cached.(*sonyflake.Sonyflake)}
	}

	return sonyflakeIDImpl{sonyflake: instance}
}

func resolveMachineID(nid NodeID, ip string) (uint16, error) {
	if nid != AutoNodeID {
		if nid < 0 || nid > NodeID(^uint16(0)) {
			return 0, fmt.Errorf("invalid node id %d, expected in [0, %d]", nid, ^uint16(0))
		}

		return uint16(nid), nil
	}

	if ip == "" {
		ip = os.Getenv(DefaultPodIPEnvKey)
	}
	if ip != "" {
		ipv4, err := parseIPv4(ip)
		if err != nil {
			return 0, fmt.Errorf("failed to parse pod ip %q: %w", ip, err)
		}

		return uint16(ipv4[2])<<8 + uint16(ipv4[3]), nil
	}

	var b [2]byte
	if _, err := rand.Read(b[:]); err != nil {
		return 0, fmt.Errorf("failed to generate random machine id: %w", err)
	}

	randomMachineID := uint16(b[0])<<8 + uint16(b[1])

	return randomMachineID, nil
}

func (s sonyflakeIDImpl) MustNew() uint64 {
	id, err := s.sonyflake.NextID()
	if err != nil {
		panic(fmt.Sprintf("failed to generate sonyflake id: %v", err))
	}

	return id
}

func parseIPv4(ip string) (net.IP, error) {
	parsed := net.ParseIP(ip)
	if parsed == nil {
		return nil, fmt.Errorf("invalid ip")
	}

	ipv4 := parsed.To4()
	if ipv4 == nil {
		return nil, fmt.Errorf("not ipv4")
	}

	return ipv4, nil
}
