package data

import (
	"errors"
	"fmt"
	"net"
	"net/netip"
)

// ErrNotFound is returned by Query when the IP parses but is not present in the DB.
var ErrNotFound = errors.New("IP not found")

func (d *IpDb) Query(ipArg string) (map[string]any, *net.IPNet, error) {
	// maxminddb-golang v2 requires a netip.Addr.
	addr, err := netip.ParseAddr(ipArg)
	if err != nil {
		return nil, nil, fmt.Errorf("invalid IP: %s", ipArg)
	}

	// Hold the read lock: Reload() reassigns d.r and closes the old reader,
	// so reading d.r / calling Lookup without synchronization is a data race.
	d.mu.RLock()
	defer d.mu.RUnlock()

	if d.r == nil {
		return nil, nil, fmt.Errorf("db not loaded")
	}

	// Look up the record for the IP.
	result := d.r.Lookup(addr)
	if err := result.Err(); err != nil {
		return nil, nil, err
	}
	if !result.Found() {
		return nil, nil, fmt.Errorf("%w: %s", ErrNotFound, ipArg)
	}

	// Decode the record into a generic map.
	anyData := make(map[string]any)
	if err := result.Decode(&anyData); err != nil {
		return nil, nil, err
	}

	// Convert the matched prefix back to *net.IPNet for callers.
	var recordNet *net.IPNet
	if prefix := result.Prefix(); prefix.IsValid() {
		recordNet = prefixToIPNet(prefix)
	}
	return anyData, recordNet, nil
}

// prefixToIPNet converts a netip.Prefix to a *net.IPNet.
func prefixToIPNet(prefix netip.Prefix) *net.IPNet {
	addr := prefix.Addr().AsSlice()
	mask := net.CIDRMask(prefix.Bits(), len(addr)*8)
	return &net.IPNet{IP: addr, Mask: mask}
}
