package httpapi

import (
	"net/http"

	"git.vakhrushev.me/av/healthlog/internal/catalog"
)

// catalogResponse — оболочка ответа каталога.
//
// Объект, а не голый массив: список метрик — не единственное, что каталогу
// когда-нибудь придётся отдать, а массив верхнего уровня расширить нечем.
type catalogResponse struct {
	Metrics []catalog.Metric `json:"metrics"`
}

// handleMetrics отдаёт каталог разрезов с измеренным родом агрегации.
//
// Список приходит из домена уже непустым срезом: nil сериализуется в `null`, и
// пустая витрина отдавала бы клиенту `"metrics": null` вместо `[]`. Тест,
// сравнивающий разобранные структуры, этого не увидел бы — потому приёмочная
// проверка сравнивает байты ответа. Второй страховки здесь нет намеренно:
// подстраховка поверх подстраховки прячет отказ первой.
//
// Условный запрос стоит ПОСЛЕ проверки токена (её ставит роутер) и ДО сборки
// снимка: в этом весь смысл — самый частый запрос потребителя есть повтор
// неизменившегося, и он не должен стоить ни снимка, ни разжатия точек.
// scopeMetrics — область действия метки каталога. Ответ маршрута не зависит от
// параметров запроса, поэтому область постоянна; у точек и MCP на её месте
// будет канонизированная форма запроса.
const scopeMetrics = "metrics"

func (a *api) handleMetrics(w http.ResponseWriter, r *http.Request) {
	// Values, а не Get: `If-None-Match` клиент вправе прислать несколькими
	// строками, и `Get` увидел бы только первую — часть меток осталась бы
	// нерассмотренной.
	if cond := r.Header.Values("If-None-Match"); len(cond) > 0 {
		// Отказ пробы глушится намеренно: он означает лишь, что условного
		// ответа не будет, — а настоящий отказ базы всплывёт сборкой каталога
		// строкой ниже и будет назван ею один раз.
		// Версию спрашиваем У КАТАЛОГА, а не у хранилища: ответ есть функция не
		// только состояния витрины, и что ещё в него входит, знает домен. Read
		// API точек ответит здесь же своей версией, включающей параметры
		// запроса, — транспорту эти правила знать незачем.
		if version, err := a.catalog.Version(r.Context()); err == nil {
			if tag := etag(scopeMetrics, version); notModified(cond, tag) {
				writeNotModified(w, tag)
				return
			}
		}
	}

	snap, err := a.catalog.Metrics(r.Context())
	if err != nil {
		// Исход операции логирует доменный слой, транспорт только переводит его
		// в ответ. Наружу уходит человекочитаемое сообщение, а не текст ошибки:
		// в нём имена колонок и форма запроса.
		writeError(w, http.StatusInternalServerError, "каталог не собрался")
		return
	}
	// Версии может не быть — витрина изменилась, пока ответ собирался. Тогда
	// ответ уходит без метки: это ровно поведение до появления условного
	// запроса, то есть деградация в безопасную сторону.
	setReadHeaders(w, etag(scopeMetrics, snap.Version))
	writeJSON(w, http.StatusOK, catalogResponse{Metrics: snap.Metrics})
}
