// Package location resolves the geographic position of this node via IP
// geolocation and applies a privacy-preserving random offset proportional
// to the chosen granularity level before publishing the result.
package location

import (
	"encoding/json"
	"fmt"
	"math/rand"
	"net/http"
	"time"

	peer "cloud.o-forge.io/core/oc-lib/models/peer"
)

// fuzzRadius returns the maximum random offset (in degrees) for each axis
// given a granularity level.
//
//	0 → no location
//	1 → continent  ±15° lat / ±20° lng
//	2 → country    ±3°  lat / ±4°  lng  (default)
//	3 → region     ±0.5° lat / ±0.7° lng
//	4 → city       ±0.05° lat / ±0.07° lng
func fuzzRadius(granularity int) (latR, lngR float64) {
	switch granularity {
	case 1:
		return 15.0, 20.0
	case 2:
		return 3.0, 4.0
	case 3:
		return 0.5, 0.7
	case 4:
		return 0.05, 0.07
	default:
		return 3.0, 4.0
	}
}

// clamp keeps a value inside [min, max].
func clamp(v, min, max float64) float64 {
	if v < min {
		return min
	}
	if v > max {
		return max
	}
	return v
}

// ipAPIResponse is the subset of fields returned by ip-api.com/json.
type ipAPIResponse struct {
	Status  string  `json:"status"`
	Lat     float64 `json:"lat"`
	Lon     float64 `json:"lon"`
	Country string  `json:"country"`
	Region  string  `json:"regionName"`
	City    string  `json:"city"`
}

// Geolocate resolves the current public IP location via ip-api.com (free,
// no key required for non-commercial use), then fuzzes the result according
// to granularity.
//
// Returns nil if granularity == 0 (opt-out) or if the lookup fails.
func Geolocate(granularity int) *peer.PeerLocation {
	if granularity == 0 {
		return nil
	}

	client := &http.Client{Timeout: 5 * time.Second}
	resp, err := client.Get("http://ip-api.com/json?fields=status,lat,lon,country,regionName,city")
	if err != nil {
		return nil
	}
	defer resp.Body.Close()

	var result ipAPIResponse
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil || result.Status != "success" {
		return nil
	}

	latR, lngR := fuzzRadius(granularity)
	rng := rand.New(rand.NewSource(time.Now().UnixNano()))

	fuzzedLat := result.Lat + (rng.Float64()*2-1)*latR
	fuzzedLng := result.Lon + (rng.Float64()*2-1)*lngR

	fuzzedLat = clamp(fuzzedLat, -85.0, 85.0)
	fuzzedLng = clamp(fuzzedLng, -180.0, 180.0)

	fmt.Printf("[location] granularity=%d raw=(%.4f,%.4f) fuzzed=(%.4f,%.4f)\n",
		granularity, result.Lat, result.Lon, fuzzedLat, fuzzedLng)

	return &peer.PeerLocation{
		Latitude:    fuzzedLat,
		Longitude:   fuzzedLng,
		Granularity: granularity,
	}
}
