package main

import (
	"context"
	"encoding/json"
	"math"
	"math/rand"
	"net/http"
	"strconv"
	"time"
)

// GET /api/gis/asset-group-markers?customer_id=&location_id=
// Returns one marker per asset group with DB icon path, asset count, and centroid lat/lng.
// Icon path maps directly to the frontend public folder (e.g. /assets/icons/pump.svg).
func assetGroupMarkersHandler(db *DB) http.HandlerFunc {
	type item struct {
		AssetGroupID   int     `json:"asset_group_id"`
		AssetGroupName string  `json:"asset_group_name"`
		Icon           string  `json:"icon"`
		AssetCount     int     `json:"asset_count"`
		Latitude       float64 `json:"latitude"`
		Longitude      float64 `json:"longitude"`
	}
	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
		defer cancel()

		customerID, errC := strconv.Atoi(r.URL.Query().Get("customer_id"))
		locationID, errL := strconv.Atoi(r.URL.Query().Get("location_id"))
		if errC != nil || errL != nil || customerID == 0 || locationID == 0 {
			w.WriteHeader(http.StatusBadRequest)
			json.NewEncoder(w).Encode(map[string]string{"error": "customer_id and location_id are required"})
			return
		}

		rows, err := db.pool.Query(ctx, `
			SELECT
				ag.subcategory_id                         AS asset_group_id,
				ag.asset_group_name,
				COALESCE(ag.icon, '')                     AS icon,
				COUNT(a.asset_id)                         AS asset_count,
				AVG(a.asset_current_lattitude)            AS latitude,
				AVG(a.asset_current_longitude)            AS longitude
			FROM ic3_asset_master a
			INNER JOIN ic3_asset_group ag ON ag.subcategory_id = a.asset_group_id
			WHERE a.customer_id = $1
			  AND a.location_id = $2
			  AND a.asset_current_lattitude  IS NOT NULL
			  AND a.asset_current_longitude IS NOT NULL
			GROUP BY ag.subcategory_id, ag.asset_group_name, ag.icon
			ORDER BY COUNT(a.asset_id) DESC
		`, customerID, locationID)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}
		defer rows.Close()

		out := []item{}
		for rows.Next() {
			var x item
			if rows.Scan(&x.AssetGroupID, &x.AssetGroupName, &x.Icon, &x.AssetCount, &x.Latitude, &x.Longitude) == nil {
				out = append(out, x)
			}
		}
		json.NewEncoder(w).Encode(out)
	}
}

// POST /api/admin/gis/generate-coordinates
// Body: { "location_id": 123, "radius_m": 500 }
//
// Generates unique random GPS coordinates within a circular boundary for all
// assets in the given location and persists them to
// ic3_asset_master.asset_current_lattitude / asset_current_longitude.
//
// Algorithm: uniform circular distribution (sqrt of random radius) so that
// markers appear naturally spread rather than clustered at the centre.
// Duplicate (lat, lng) pairs are rejected and regenerated automatically.
func generateCoordinatesHandler(db *DB) http.HandlerFunc {
	type locInfo struct {
		LocationID   int     `json:"location_id"`
		LocationName string  `json:"location_name"`
		Lat          float64 `json:"lat"`
		Lng          float64 `json:"lng"`
	}
	type resp struct {
		Location     locInfo `json:"location"`
		RadiusM      float64 `json:"radius_m"`
		UpdatedCount int     `json:"updated_count"`
	}

	return func(w http.ResponseWriter, r *http.Request) {
		ctx, cancel := context.WithTimeout(r.Context(), 30*time.Second)
		defer cancel()

		var req struct {
			LocationID int     `json:"location_id"`
			RadiusM    float64 `json:"radius_m"`
		}
		if err := json.NewDecoder(r.Body).Decode(&req); err != nil || req.LocationID == 0 {
			w.WriteHeader(http.StatusBadRequest)
			json.NewEncoder(w).Encode(map[string]string{"error": "location_id required"})
			return
		}
		if req.RadiusM <= 0 {
			req.RadiusM = 500
		}
		if req.RadiusM > 5000 {
			req.RadiusM = 5000
		}

		// Validate location and retrieve GPS centre
		var loc locInfo
		var latPtr, lngPtr *float64
		if err := db.pool.QueryRow(ctx,
			`SELECT location_id, location_name, latitude, longitude
			 FROM ic3_location_master WHERE location_id = $1`, req.LocationID,
		).Scan(&loc.LocationID, &loc.LocationName, &latPtr, &lngPtr); err != nil {
			w.WriteHeader(http.StatusNotFound)
			json.NewEncoder(w).Encode(map[string]string{"error": "location not found"})
			return
		}
		if latPtr == nil || lngPtr == nil {
			w.WriteHeader(http.StatusUnprocessableEntity)
			json.NewEncoder(w).Encode(map[string]string{
				"error": "Unable to generate asset coordinates. Location latitude/longitude not configured.",
			})
			return
		}
		loc.Lat = *latPtr
		loc.Lng = *lngPtr

		// Collect asset IDs for this location
		rows, err := db.pool.Query(ctx,
			`SELECT asset_id FROM ic3_asset_master WHERE location_id = $1 ORDER BY asset_id`,
			req.LocationID)
		if err != nil {
			w.WriteHeader(500)
			json.NewEncoder(w).Encode(map[string]string{"error": err.Error()})
			return
		}
		var ids []int
		for rows.Next() {
			var id int
			if rows.Scan(&id) == nil {
				ids = append(ids, id)
			}
		}
		rows.Close()

		if len(ids) == 0 {
			w.Header().Set("Content-Type", "application/json")
			json.NewEncoder(w).Encode(resp{Location: loc, RadiusM: req.RadiusM, UpdatedCount: 0})
			return
		}

		coords := circularCoords(loc.Lat, loc.Lng, req.RadiusM, len(ids))

		updated := 0
		for i, assetID := range ids {
			if i >= len(coords) {
				break
			}
			tag, e := db.pool.Exec(ctx,
				`UPDATE ic3_asset_master
				 SET asset_current_lattitude=$1, asset_current_longitude=$2
				 WHERE asset_id=$3`,
				coords[i][0], coords[i][1], assetID,
			)
			if e == nil && tag.RowsAffected() > 0 {
				updated++
			}
		}

		w.Header().Set("Content-Type", "application/json")
		json.NewEncoder(w).Encode(resp{Location: loc, RadiusM: req.RadiusM, UpdatedCount: updated})
	}
}

// circularCoords returns n unique (lat, lng) pairs uniformly distributed
// inside a circle of radiusM metres centred on (centerLat, centerLng).
// Coordinates are rounded to 6 decimal places for GIS compatibility.
func circularCoords(centerLat, centerLng, radiusM float64, n int) [][2]float64 {
	const latMPerDeg = 111000.0
	lngMPerDeg := 111000.0 * math.Cos(centerLat*math.Pi/180.0)

	rng := rand.New(rand.NewSource(time.Now().UnixNano()))
	seen := make(map[[2]float64]bool, n)
	out := make([][2]float64, 0, n)

	for len(out) < n {
		angle := rng.Float64() * 2 * math.Pi
		dist := radiusM * math.Sqrt(rng.Float64()) // sqrt → uniform over disk area
		lat := math.Round((centerLat+dist/latMPerDeg*math.Cos(angle))*1e6) / 1e6
		lng := math.Round((centerLng+dist/lngMPerDeg*math.Sin(angle))*1e6) / 1e6
		key := [2]float64{lat, lng}
		if !seen[key] {
			seen[key] = true
			out = append(out, key)
		}
	}
	return out
}
