package http

import (
	"net/http"
	"strings"
)

// Отдельные адреса наблюдения. Корней у сервиса остался один — корень
// приложения; `/api` и `/_` ушли вместе со встроенным хранилищем и его панелью,
// и адресов под этими именами не существует.
//
// Резервировать имя за отказом сервис не берётся: имя, за которым ничего не
// стоит, ничем не отличается от любого другого свободного, а второй перечень
// «когда-то занятых корней» разошёлся бы с первым молча.
const (
	HealthPath  = "/health"
	MetricsPath = "/metrics"
)

// Mount — часть адресного пространства, принадлежащая сервису.
//
// Перечень этих частей — **единственное** описание того, что сервису
// принадлежит, и он не описывает регистрацию, а порождает её: корень,
// заведённый мимо перечня, не получит обработчика вовсе. Из него же выводятся
// правило неизвестного пути, уровень журнала и область действия узнавания.
type Mount struct {
	// Path — корень либо точный адрес.
	Path string

	// Exact — путь является точным адресом, а не корнем: `/health` накрывает
	// только сам себя, а `/app` — всё, что под ним.
	Exact bool

	// Bind вешает обработчики этой части.
	Bind func(mux *http.ServeMux)
}

// Covers говорит, принадлежит ли путь этой части адресного пространства.
//
// Условий два, и оба обязательны: точное совпадение либо префикс **вместе с
// косой чертой**. По одному префиксу корню `/app` достался бы посторонний
// `/apple`; по одному префиксу с косой чертой голый `/app` не достался бы никому
// и уехал бы разметкой приложения.
func (m Mount) Covers(requestPath string) bool {
	if m.Exact {
		return requestPath == m.Path
	}

	return requestPath == m.Path || strings.HasPrefix(requestPath, m.Path+"/")
}

// ServiceMounts перечисляет адресное пространство сервиса целиком.
//
// Обработчик приложения приходит уже одетым в свои слои — ограничитель частоты,
// узнавание, требование учётной записи: область их действия и есть корень
// приложения, и берётся она отсюда, а не перечисляется вторым списком.
func ServiceMounts(app http.Handler, metricsHandler http.Handler) []Mount {
	return []Mount{
		{Path: AppRoot, Bind: bindApp(app)},
		{Path: HealthPath, Exact: true, Bind: bindHealth},
		{Path: MetricsPath, Exact: true, Bind: bindMetrics(metricsHandler)},
	}
}

// RegisterServiceRoutes вешает всё, что сервис вешает сам.
func RegisterServiceRoutes(mux *http.ServeMux, mounts []Mount) {
	for _, mount := range mounts {
		if mount.Bind != nil {
			mount.Bind(mux)
		}
	}
}

// ExactAddressOf называет точный адрес сервиса, которому отвечает путь. Второе
// значение ложно у всего прочего — у пути под корнем и у пути вне корней.
//
// Точные адреса — закрытый перечень, и только они пишутся в журнал дословно:
// значением такого пути распоряжается не спрашивающий, а сам перечень.
func ExactAddressOf(mounts []Mount, requestPath string) (string, bool) {
	for _, mount := range mounts {
		if mount.Exact && mount.Covers(requestPath) {
			return mount.Path, true
		}
	}

	return "", false
}

// IsObservationAddress говорит, что путь — адрес наблюдения.
//
// Опрос здоровья и метрик идёт постоянно и полезного не несёт, поэтому уровень
// журнала у него свой. Перечень при этом тот же самый: второе перечисление этих
// адресов разошлось бы с первым молча.
func IsObservationAddress(mounts []Mount, requestPath string) bool {
	_, ok := ExactAddressOf(mounts, requestPath)
	return ok
}

// bindApp вешает корень приложения.
//
// Образцов два, и оба обязательны: без точного `/app` маршрутизатор увёл бы
// голый корень перенаправлением на `/app/`, а норма требует от него отказа
// приложения, а не переезда.
func bindApp(app http.Handler) func(mux *http.ServeMux) {
	return func(mux *http.ServeMux) {
		mux.Handle(AppRoot+"/", app)
		mux.Handle(AppRoot, app)
	}
}

func bindHealth(mux *http.ServeMux) {
	mux.HandleFunc(HealthPath, func(w http.ResponseWriter, _ *http.Request) {
		writeJSON(w, http.StatusOK, map[string]string{
			"status":  "ok",
			"message": "Transcriber service is running",
		})
	})
}

func bindMetrics(handler http.Handler) func(mux *http.ServeMux) {
	return func(mux *http.ServeMux) {
		mux.Handle(MetricsPath, handler)
	}
}
