package main

// HTTP routes for the empyrion player-history + inventory-template tables.

import (
	"context"
	"encoding/json"
	"errors"
	"io"
	"net/http"
	"strconv"
	"strings"
)

// GET /api/instances/{id}/empyrion-history/players
// Returns the projected known_players for the instance, ordered by last_seen DESC.
func (h *httpServer) handleEmpyrionHistoryPlayers(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	rows, err := h.db.Pool().Query(r.Context(), `
		SELECT player_id, steam_id, player_name, first_seen, last_seen, login_count, last_playfield, notes
		FROM empyrion_known_players
		WHERE instance_id = $1
		ORDER BY last_seen DESC LIMIT 500`, id)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	defer rows.Close()
	type p struct {
		PlayerID      int    `json:"playerId"`
		SteamID       string `json:"steamId"`
		PlayerName    string `json:"playerName"`
		FirstSeen     string `json:"firstSeen"`
		LastSeen      string `json:"lastSeen"`
		LoginCount    int    `json:"loginCount"`
		LastPlayfield string `json:"lastPlayfield"`
		Notes         string `json:"notes"`
	}
	out := []p{}
	for rows.Next() {
		var pp p
		var firstSeen, lastSeen any
		if err := rows.Scan(&pp.PlayerID, &pp.SteamID, &pp.PlayerName, &firstSeen, &lastSeen, &pp.LoginCount, &pp.LastPlayfield, &pp.Notes); err != nil {
			continue
		}
		pp.FirstSeen = formatTS(firstSeen)
		pp.LastSeen = formatTS(lastSeen)
		out = append(out, pp)
	}
	writeJSON(w, http.StatusOK, map[string]any{"players": out, "count": len(out)})
}

// PUT /api/instances/{id}/empyrion-history/players/{playerId}/notes
// Body: raw text (max 4096 chars). Updates only when a row already exists —
// known_players is projected from the event log so we never insert here.
func (h *httpServer) handleEmpyrionPlayerNotesUpdate(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	pid, err := strconv.Atoi(r.PathValue("playerId"))
	if err != nil || pid <= 0 {
		writeError(w, http.StatusBadRequest, "bad_player_id", "playerId must be a positive integer")
		return
	}
	body, err := io.ReadAll(io.LimitReader(r.Body, 4096))
	if err != nil {
		writeError(w, http.StatusBadRequest, "bad_body", err.Error())
		return
	}
	notes := strings.TrimRight(string(body), "\r\n")
	tag, err := h.db.Pool().Exec(r.Context(),
		`UPDATE empyrion_known_players SET notes=$3 WHERE instance_id=$1 AND player_id=$2`,
		id, pid, notes)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	if tag.RowsAffected() == 0 {
		writeError(w, http.StatusNotFound, "no_player", "player has no history row yet — they must log in at least once")
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"playerId": pid, "notes": notes})
}

// GET /api/instances/{id}/empyrion-history/events
// Recent event log. Query: ?limit=200&player_id=42&cmd=Event_ChatMessage
func (h *httpServer) handleEmpyrionHistoryEvents(w http.ResponseWriter, r *http.Request) {
	id := r.PathValue("id")
	limit, _ := strconv.Atoi(r.URL.Query().Get("limit"))
	if limit <= 0 || limit > 1000 {
		limit = 200
	}
	q := `SELECT ts, cmd, player_id, steam_id, player_name, playfield, msg FROM empyrion_events WHERE instance_id = $1`
	args := []any{id}
	if pid := r.URL.Query().Get("player_id"); pid != "" {
		args = append(args, pid)
		q += " AND player_id = $" + strconv.Itoa(len(args))
	}
	if cmd := r.URL.Query().Get("cmd"); cmd != "" {
		args = append(args, cmd)
		q += " AND cmd = $" + strconv.Itoa(len(args))
	}
	args = append(args, limit)
	q += " ORDER BY ts DESC LIMIT $" + strconv.Itoa(len(args))
	rows, err := h.db.Pool().Query(r.Context(), q, args...)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	defer rows.Close()
	type e struct {
		TS         string  `json:"ts"`
		Cmd        string  `json:"cmd"`
		PlayerID   *int    `json:"playerId,omitempty"`
		SteamID    string  `json:"steamId,omitempty"`
		PlayerName string  `json:"playerName,omitempty"`
		Playfield  string  `json:"playfield,omitempty"`
		Msg        string  `json:"msg,omitempty"`
	}
	out := []e{}
	for rows.Next() {
		var ee e
		var ts any
		var pid *int
		var steam, name, pf, msg *string
		if err := rows.Scan(&ts, &ee.Cmd, &pid, &steam, &name, &pf, &msg); err != nil {
			continue
		}
		ee.TS = formatTS(ts)
		ee.PlayerID = pid
		if steam != nil { ee.SteamID = *steam }
		if name != nil { ee.PlayerName = *name }
		if pf != nil { ee.Playfield = *pf }
		if msg != nil { ee.Msg = *msg }
		out = append(out, ee)
	}
	writeJSON(w, http.StatusOK, map[string]any{"events": out, "count": len(out)})
}

func formatTS(v any) string {
	if v == nil {
		return ""
	}
	type fmtTime interface{ Format(string) string }
	if t, ok := v.(fmtTime); ok {
		return t.Format("2006-01-02 15:04:05")
	}
	return ""
}

// --- Inventory templates ---

// GET /api/empyrion/templates  — list all templates (global, not per-instance).
func (h *httpServer) handleEmpyrionTemplatesList(w http.ResponseWriter, r *http.Request) {
	rows, err := h.db.Pool().Query(r.Context(), `
		SELECT id, name, description, items, created_at, updated_at
		FROM empyrion_inv_templates ORDER BY name`)
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	defer rows.Close()
	type t struct {
		ID          int64           `json:"id"`
		Name        string          `json:"name"`
		Description string          `json:"description"`
		Items       json.RawMessage `json:"items"`
		CreatedAt   string          `json:"createdAt"`
		UpdatedAt   string          `json:"updatedAt"`
	}
	out := []t{}
	for rows.Next() {
		var tt t
		var created, updated any
		if err := rows.Scan(&tt.ID, &tt.Name, &tt.Description, &tt.Items, &created, &updated); err != nil {
			continue
		}
		tt.CreatedAt = formatTS(created)
		tt.UpdatedAt = formatTS(updated)
		out = append(out, tt)
	}
	writeJSON(w, http.StatusOK, map[string]any{"templates": out})
}

// POST /api/empyrion/templates  — create or update by name.
func (h *httpServer) handleEmpyrionTemplatesUpsert(w http.ResponseWriter, r *http.Request) {
	var req struct {
		ID          int64             `json:"id"`
		Name        string            `json:"name"`
		Description string            `json:"description"`
		Items       []map[string]any  `json:"items"`
	}
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		writeError(w, http.StatusBadRequest, "bad_json", err.Error())
		return
	}
	if strings.TrimSpace(req.Name) == "" {
		writeError(w, http.StatusBadRequest, "missing", "name is required")
		return
	}
	itemsJSON, err := json.Marshal(req.Items)
	if err != nil {
		writeError(w, http.StatusBadRequest, "bad_items", err.Error())
		return
	}
	if req.ID > 0 {
		_, err = h.db.Pool().Exec(r.Context(), `
			UPDATE empyrion_inv_templates SET name=$2, description=$3, items=$4::jsonb, updated_at=NOW() WHERE id=$1`,
			req.ID, req.Name, req.Description, string(itemsJSON))
	} else {
		_, err = h.db.Pool().Exec(r.Context(), `
			INSERT INTO empyrion_inv_templates (name, description, items) VALUES ($1, $2, $3::jsonb)`,
			req.Name, req.Description, string(itemsJSON))
	}
	if err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	h.handleEmpyrionTemplatesList(w, r)
}

// DELETE /api/empyrion/templates/{id}
func (h *httpServer) handleEmpyrionTemplatesDelete(w http.ResponseWriter, r *http.Request) {
	id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
	if err != nil {
		writeError(w, http.StatusBadRequest, "bad_id", "id must be integer")
		return
	}
	if _, err := h.db.Pool().Exec(r.Context(), `DELETE FROM empyrion_inv_templates WHERE id=$1`, id); err != nil {
		writeError(w, http.StatusInternalServerError, "db", err.Error())
		return
	}
	writeJSON(w, http.StatusOK, map[string]any{"deleted": id})
}

// POST /api/instances/{id}/empyrion/templates/{tid}/apply/{playerId}
// Reads the template's items list and fans out give-item calls to the bridge.
func (h *httpServer) handleEmpyrionTemplateApply(w http.ResponseWriter, r *http.Request) {
	instanceID := r.PathValue("id")
	tid, _ := strconv.ParseInt(r.PathValue("tid"), 10, 64)
	playerID, _ := strconv.Atoi(r.PathValue("playerId"))
	if tid == 0 || playerID == 0 {
		writeError(w, http.StatusBadRequest, "bad", "tid and playerId required")
		return
	}
	var itemsJSON []byte
	err := h.db.Pool().QueryRow(r.Context(), `SELECT items FROM empyrion_inv_templates WHERE id=$1`, tid).Scan(&itemsJSON)
	if err != nil {
		writeError(w, http.StatusNotFound, "no_template", err.Error())
		return
	}
	var items []map[string]any
	if err := json.Unmarshal(itemsJSON, &items); err != nil {
		writeError(w, http.StatusInternalServerError, "bad_template", err.Error())
		return
	}
	tgt, err := h.resolveEmpyrionTarget(r.Context(), instanceID)
	if err != nil {
		writeError(w, http.StatusServiceUnavailable, "no_bridge", err.Error())
		return
	}

	successes, failures := 0, []string{}
	for _, it := range items {
		body, _ := json.Marshal(it)
		ok, msg := postBridgeJSON(r.Context(), tgt, "/api/v1/players/"+strconv.Itoa(playerID)+"/give-item", body)
		if ok {
			successes++
		} else {
			failures = append(failures, msg)
		}
	}
	writeJSON(w, http.StatusOK, map[string]any{
		"applied":   successes,
		"failed":    len(failures),
		"failures":  failures,
		"playerId":  playerID,
		"templateId": tid,
	})
}

// postBridgeJSON is a small helper for one-shot POSTs to the bridge from
// inside non-proxy controller handlers.
func postBridgeJSON(ctx context.Context, tgt *empyrionTarget, path string, body []byte) (bool, string) {
	url := strings.TrimRight(tgt.bridgeURL, "/") + path + "?host=" + tgt.host + "&port=" + strconv.Itoa(tgt.port)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, strings.NewReader(string(body)))
	if err != nil {
		return false, err.Error()
	}
	req.Header.Set("Content-Type", "application/json")
	if tgt.bridgeToken != "" {
		req.Header.Set("Authorization", "Bearer "+tgt.bridgeToken)
	}
	resp, err := (&http.Client{}).Do(req)
	if err != nil {
		return false, err.Error()
	}
	defer resp.Body.Close()
	out, _ := io.ReadAll(resp.Body)
	if resp.StatusCode >= 400 {
		return false, string(out)
	}
	_ = errors.New
	return true, ""
}
