package main

// Structure-duplicate detection.
//
// Queries the latest Event_GlobalStructure_List per structure from
// empyrion_events and groups by (LOWER(name), block_count_rounded_to_10)
// to surface likely dupes.
//
// ASSUMPTION about empyrion_events.raw JSON shape for
// Event_GlobalStructure_List entries:
//
//	{
//	  "id": <int>,
//	  "name": <string>,
//	  "blocks": <int>,
//	  "playfield": <string>,
//	  "factionId": <int>
//	}
//
// This matches the EAH bridge's Event_GlobalStructure_List serialization.
// If the actual shape differs, adjust the JSON keys in the SQL below.

import (
	"encoding/json"
	"net/http"
	"strconv"
)

// dupeGroup is one group of suspected-duplicate structures.
type dupeGroup struct {
	Name        string       `json:"name"`
	BlockCount  int          `json:"blockCount"`
	Members     []dupeMember `json:"members"`
}

type dupeMember struct {
	ID        int64  `json:"id"`
	Playfield string `json:"playfield"`
	FactionID int    `json:"factionId"`
}

// ---------------------------------------------------------------------------
// GET /api/instances/{id}/empyrion/dupes?min_blocks=&min_match=
// ---------------------------------------------------------------------------

func (h *httpServer) handleEmpyrionDupes(w http.ResponseWriter, r *http.Request) {
	instanceID := r.PathValue("id")

	minBlocks, _ := strconv.Atoi(r.URL.Query().Get("min_blocks"))
	if minBlocks <= 0 {
		minBlocks = 0
	}
	minMatch, _ := strconv.Atoi(r.URL.Query().Get("min_match"))
	if minMatch < 2 {
		minMatch = 2
	}

	// Use PostgreSQL JSON functions on the raw column to extract fields
	// from Event_GlobalStructure_List events.
	rows, err := h.db.Pool().Query(r.Context(), `
		WITH latest_struct AS (
			SELECT DISTINCT ON ((raw->>'id')::bigint)
				(raw->>'id')::bigint AS id,
				raw->>'name' AS name,
				COALESCE((raw->>'blocks')::int, 0) AS blocks,
				raw->>'playfield' AS playfield,
				COALESCE((raw->>'factionId')::int, 0) AS faction_id,
				ts
			FROM empyrion_events
			WHERE instance_id = $1 AND cmd = 'Event_GlobalStructure_List'
			  AND raw ? 'id' AND raw ? 'name'
			ORDER BY (raw->>'id')::bigint, ts DESC
		)
		SELECT
			LOWER(name) AS normalized_name,
			(blocks / 10) * 10 AS rounded_blocks,
			json_agg(json_build_object('id', id, 'playfield', playfield, 'factionId', faction_id)) AS members
		FROM latest_struct
		WHERE blocks >= $2
		GROUP BY LOWER(name), (blocks / 10) * 10
		HAVING count(*) >= $3
		ORDER BY LOWER(name)`,
		instanceID, minBlocks, minMatch)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	defer rows.Close()

	out := make([]dupeGroup, 0, 32)
	for rows.Next() {
		var g dupeGroup
		var membersJSON []byte
		if err := rows.Scan(&g.Name, &g.BlockCount, &membersJSON); err != nil {
			continue
		}
		// Unmarshal the json_agg result into the members slice.
		if err := json.Unmarshal(membersJSON, &g.Members); err != nil {
			continue
		}
		out = append(out, g)
	}
	writeJSON(w, http.StatusOK, map[string]any{"groups": out})
}
